Curried functions are probably one of the first new things you come across if you go from an imperative language to a functional language. In purely functional languages, the convention is to define an n-parameter function inductively by staggering the parameters: applying the function to argument #1 returns a function that takes parameters 2..n, which in turn can be applied to argument #2 to return a function that takes parameters 3..n, etc. until all the arguments are given and the result is returned. For instance, we can define a 3-parameter add function that adds three numbers:
add x y z = x + y + z add = \x -> (\y -> (\z -> x + y + z)) add :: Int -> (Int -> (Int -> Int)) ((add 1 ) 2 ) 3
We make the arrow -> right-associative, so we can write Int -> Int -> Int -> Int . Additionally, we make function application left-associative, so we can write add 1 2 3 and minimize our use of parentheses.
I want to argue that when you define functions in this style, although it is nice and elegant, there are also some things that are lost.
There are roughly three different "styles" that programming languages offer for writing multi-parameter function definitions with. Firstly, there is the imperative style which I will call the "parameter list" style. Here, multiple parameters is a baked-in feature of functions. This is the default in imperative languages like Rust:
fn f(p1: P1 , p2: P2 , p3: P3 ) -> R { ... } f(a1, a2, a3) f : fn ( P1 , P2 , P3 ) -> R
Another form is the "curried" style, offered in pure functional languages like Haskell:
f p1 p2 p3 = ... f a1 a2 a3 f :: P1 -> P2 -> P3 -> R
Finally, there is the "tuple" style. It looks similar to the parameter list style, but the multiple parameters are not part of the functions itself. The function just has one parameter, but that parameter is a tuple so it effectively carries multiple values. This is usually possible in functional languages like Haskell as well but not the standard:
f(p1, p2, p3) = ... f(a1, a2, a3) f :: ( P1 , P2 , P3 ) -> R
... continue reading