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 + y
Let’s break down this syntax piece-by-piece.
The first line is the type signature.
add
is 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 typea
must be an instance of theNum
typeclass. We need this type constraint because the(+)
function isn’t defined for every combination of types as inputs; what happens if you add aApplicationState
and aMinHeap
? You can use:t (+)
inghci
(a Haskell repl) to see that addition is only defined for instances ofNum
. You can use:i Num
to see thatNum
is a class containing types that make sense to add:Integer
,Double
,Float
, etc. By saying(Num a)
, we’re defining a variablea
that is a specific instance of theNum
typeclass. 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 -> a
is the real meat of the type. Witha
already defined, this means we’re looking at aInteger -> Integer -> Integer
, aDouble->Double->Double
, etc. This->
syntax is Haskell’s way of denoting functions.Integer -> String
is a function that mapsInteger
s toString
s. With multiple arrows (Int->Int->Int
), we’re writing a curried1 function. For now you can look at as a function taking in twoInt
s and returning one. Generalizing theInt
type to thea
we defined in our type constraint, we get a function taking in twoa
s and returning onea
.
The second line it the implementation.
add
is the name we gave above. It’s how we know what we’re defining.x y
binds these local identifiersx
andy
to thea
s that a caller will supply our function.=
separates these bindings on the left from our production of output on the right.x + y
calls 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
6
Nice.
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 y
Let’s break this new function down piece-by-piece.
The first line is the type signature.
add5
is its name.::
separates the name from the type.(Num a)
asserts thata
is a specific instance ofNum
.a -> a
is a function mapping the domain ofa
to a domain ina
.
The second line is the implementation.
add 5
is the name.y
is a local identifier bound to whatever parameter the caller suppliesadd5
with.=
separates.add 5 y
calls theadd
function we made above, forcingx
to 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
6
Nice.
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. ↩