They are isomorphic in the strong sense that their logical interpretations are identical. Applying Curry-Howard, a function type is an implication, so a curried function with type A -> B -> C is equivalent to an implication that says "If A, then if B, then C." Likewise, a tuple is a conjunction, so a non-curried function with type (A, B) -> C is equivalent to the logic statement (A /\ B) -> C, i.e., "If A and B then C." Both logical statements are equivalent, i.e., have the same truth tables.
However, as the article outlines, there are differences (both positive and negative) to using functions with these types. Curried functions allow for partial application, leading to elegant definitions, e.g., in Haskell, we can define a function that sums over lists as sum = foldl (+) 0 where we leave out foldl's final list argument, giving us a function expecting a list that performs the behavior we expect. However, this style of programming can lead to weird games and unweildy code because of the positional nature of curried functions, e.g., having to use function combinators such as Haskell's flip function (with type (A -> B -> C) -> B -> A -> C) to juggle arguments you do not want to fill to the end of the parameter list.
Please see my other comment below, and maybe re-read the article. I'm not asking what the difference is between curried and non-curried. The article draws a three way distinction, while I'm asking why two of them should be considered distinct, and not the pair you're referring to.
Apologies, I was focused on the usual pairing in this space and not the more subtle one you're talking about. As others have pointed out, there isn't really semantic a difference between the two. Both approaches to function parameters produce the same effect. The differences are purely in "implementation," either theoretically or in terms of systems-building.
From a theoretical perspective, a tuple expresses the idea of "many things" and a multi-argument parameter list expresses the idea of both "many things" and "function arguments." Thus, from a cleanliness perspective for your definitions, you may want to separate the two, i.e., require function have exactly one argument and then pass a tuple when multiple arguments are required. This theoretical cleanliness does result in concrete gains: writing down a formalism for single-argument functions is decidedly cleaner (in my opinion) than multi-argument functions and implementing a basic interpreter off of this formalism is, subsequently, easier.
From a systems perspective, there is a clear downside in this space. If tuples exist on the heap (as they do for most functional languages), you induce a heap allocation when you want to pass multiple arguments! This pitfall is evident with the semi-common beginner's mistake with OCaml algebraic datatype definitions where the programmer inadvertently wraps the constructor type with parentheses, thereby specifying a constructor of one-argument that is a tuple instead of a multi-argument constructor (see https://stackoverflow.com/questions/67079629/is-a-multiple-a... for more details).
The highlights of Google's latest AI push for higher education:
- "Starting today, students (ages 18+) in the U.S. as well as in Japan, Indonesia, Korea and Brazil can sign-up for a 12 month Google AI Pro plan for free."
- "To support their success, we’re providing $1 billion in funding over three years for American education, including AI literacy programs, research funding and cloud computing resources. We’re also announcing the Google AI for Education Accelerator, an initiative to offer free AI training and Google Career Certificates to every college student in America. Over 100 public universities, including the University of Michigan, The Ohio State University, the University of Virginia and the University Systems in Texas, North Carolina, and Pennsylvania have already signed up. We invite all accredited non-profit colleges and universities in the U.S. to apply."
Actually, a test _is_ a proof! Or more specifically, a traditional test case is a narrow, specific proposition. For example, the test `length([1, 2, 3]) = 3` is a proposition about the behavior of the `length` function on a concrete input value. The proof of this proposition is _automatically generated_ by the runtime, i.e., the proof that this proposition holds is the execution of the left-hand side of the equality and observing it is identical to the right-hand side. In this sense, the runtime serves as an automated theorem prover (and is, perhaps, why test cases are the most accessible form of formal reasoning available to a programmer).
What we colloquially consider "proof" are really more abstract propositions (e.g., using first-order logic) that require reasoning beyond simple program execution. While the difference is, in some sense, academic, it is important to observe that testing and proving (in that colloquial sense) are, at their core, the same endeavor.
However, as the article outlines, there are differences (both positive and negative) to using functions with these types. Curried functions allow for partial application, leading to elegant definitions, e.g., in Haskell, we can define a function that sums over lists as sum = foldl (+) 0 where we leave out foldl's final list argument, giving us a function expecting a list that performs the behavior we expect. However, this style of programming can lead to weird games and unweildy code because of the positional nature of curried functions, e.g., having to use function combinators such as Haskell's flip function (with type (A -> B -> C) -> B -> A -> C) to juggle arguments you do not want to fill to the end of the parameter list.