This is a quick tutorial on partial function application for those foreign to Haskell.
Add
Consider a function that adds two numbers:
add :: (Num a) => a -> a -> a
add x y = x + yLet’s break down this syntax piece-by-piece.
The first line is the type signature.
addis its name.::separates the name from its type.(Num a)is a type constraint. It asserts that the 2 parameters and the 1 output of this function are all of the same typea, and that this typeamust be an instance of theNumtypeclass. We need this type constraint because the(+)function isn’t defined for every combination of types as inputs; what happens if you add aApplicationStateand aMinHeap? You can use:t (+)inghci(a Haskell repl) to see that addition is only defined for instances ofNum. You can use:i Numto see thatNumis a class containing types that make sense to add:Integer,Double,Float, etc. By saying(Num a), we’re defining a variableathat is a specific instance of theNumtypeclass. Later in our function signature, when we usea, we’re referring to eitherInteger,Double,Float, etc. Not a particular value of this type, but the type itself.=>separates the type constraint(s) from the typea -> a -> ais the real meat of the type. Withaalready defined, this means we’re looking at aInteger -> Integer -> Integer, aDouble->Double->Double, etc. This->syntax is Haskell’s way of denoting functions.Integer -> Stringis a function that mapsIntegers toStrings. With multiple arrows (Int->Int->Int), we’re writing a curried1 function. For now you can look at as a function taking in twoInts and returning one. Generalizing theInttype to theawe defined in our type constraint, we get a function taking in twoas and returning onea.
The second line it the implementation.
addis the name we gave above. It’s how we know what we’re defining.x ybinds these local identifiersxandyto theas that a caller will supply our function.=separates these bindings on the left from our production of output on the right.x + ycalls another function,(+), with the two inputs we received. We’re essentially dropshipping the(+)function and renaming itadd.
That’s it. We now have a function add that adds two numbers.
Use it like so:
ghci> add 5 5
10
ghci> add 5 1
6Nice.
Add5
Adding two numbers is cool, but what if we want to write a program that takes in a number and adds it to 5? It would be kind of hard to expect callers to always supply our function add x y with x=5, so let’s force it upon them.
add5 :: (Num a) => a -> a
add5 y = add 5 yLet’s break this new function down piece-by-piece.
The first line is the type signature.
add5is its name.::separates the name from the type.(Num a)asserts thatais a specific instance ofNum.a -> ais a function mapping the domain ofato a domain ina.
The second line is the implementation.
add 5is the name.yis a local identifier bound to whatever parameter the caller suppliesadd5with.=separates.add 5 ycalls theaddfunction we made above, forcingxto be5.
What we’re doing with add 5 y is called partial function application. We have this function add :: Num -> Num -> Num, we supply the first of two arguments with a Num, and get out a function of type Num -> Num.
Let’s use add5.
ghci> add5 5
10
ghci> add5 1
6Nice.
Footnotes
-
Currying is the process of transforming a function that takes multiple arguments into a sequence of functions, each with a single argument. In Haskell, all functions are curried by default, meaning that a function with multiple parameters can be partially applied by supplying fewer arguments than it expects, resulting in a new function that takes the remaining arguments. ↩