Currying and Partial Application
If you have read some of the earlier posts on this site, we saw examples of code in Haskell. What was up with those arrows in the function type declaration? If you recall, this is how we declared a function that takes in two integers as input and returns an integer as output:
addTwoNums :: Integer -> Integer -> Integer
addTwoNums a b = a + b
You would think that type declaration for such a function might look something like this instead:
addTwoNums :: Integer Integer --> Integer
The arrow syntax is not a quirk or stylistic choice - it’s actually telling you something precise about how the function works under the hood.
The Arrow is Right-Associative
Here’s the key: -> in Haskell is right-associative. That means:
Integer -> Integer -> Integer
is really:
Integer -> (Integer -> Integer)
So addTwoNums is not a function that takes two integers. It’s a function that takes one integer and returns another function - specifically, a function that takes an integer and returns an integer. Every Haskell function takes exactly one argument. Period.
This might seem like a technicality, but it has real consequences. Let’s spell it out:
-- addTwoNums applied to 3 returns a function
addThree :: Integer -> Integer
addThree = addTwoNums 3
addThree 4 -- 7
addThree 10 -- 13
addTwoNums 3 is a perfectly valid expression. It doesn’t cause an error. It just returns the partially applied function, waiting for its second argument. This is partial application - calling a function with fewer arguments than it can ultimately consume.
What is Currying?
This transformation - of a function that takes multiple arguments into a chain of single-argument functions - is called currying. It’s named after the mathematician Haskell Curry (yes, the language is named after him too). Although credit also goes to Moses Schönfinkel, who worked out the idea earlier and presumably had worse naming luck.
In Haskell, all functions are curried automatically. You don’t have to do anything special. When you write:
addTwoNums a b = a + b
Haskell interprets this as:
addTwoNums a = \b -> a + b
Which is a function that takes a and returns a lambda waiting for b. The multi-argument syntax is just convenient shorthand.
Function Application is Left-Associative
Here’s the flip side. While the type arrow is right-associative, function application is left-associative. So:
addTwoNums 3 4
parses as:
(addTwoNums 3) 4
First apply addTwoNums to 3, get back a function, then apply that to 4. The two associativities - right for types, left for application - fit together perfectly!
Why Partial Application Is Useful
Partial application is not just a theoretical curiosity. It’s genuinely handy when you want to create a specialized function from a general one.
multiply :: Integer -> Integer -> Integer
multiply x y = x * y
double :: Integer -> Integer
double = multiply 2
triple :: Integer -> Integer
triple = multiply 3
map double [1, 2, 3, 4, 5] -- [2, 4, 6, 8, 10]
map triple [1, 2, 3, 4, 5] -- [3, 6, 9, 12, 15]
No lambda needed. multiply 2 is the doubling function. This pairs beautifully with map, filter, and foldr - all of which take functions as arguments and benefit from having small, specialized functions passed in.
The standard library leans on this heavily. elem checks whether a value appears in a list:
elem :: Eq a => a -> [a] -> Bool
elem 3 [1, 2, 3, 4] -- True
So you can do things like:
filter (elem 'a') ["apple", "fig", "grape", "kiwi"]
-- ["apple", "grape"]
elem 'a' is a partially applied function of type [Char] -> Bool - exactly what filter wants.
Contrast with Non-Curried Languages
Most languages don’t curry automatically. In Python, you’d need functools.partial to get the same effect:
from functools import partial
def multiply(x, y):
return x * y
double = partial(multiply, 2)
double(5) # 10
JavaScript got a bit closer when arrow functions made returning functions less verbose:
const multiply = x => y => x * y;
const double = multiply(2);
double(5); // 10
But you have to opt in to this style manually. Haskell bakes it in at the type level, which means every function in the entire language - including library functions, operators, everything - is composable this way by default. That’s a pretty nice property!
Once you understand that -> in a type signature just means “returns a function”, the rest falls into place. Multi-argument functions are single-argument functions that return functions, all the way down. It sounds funny, but it makes partial application feel like the most natural thing in the world!