As I thought about last night’s post, I was bothered by how confusing it seemed and I finally decided that the reason is that I never directly addressed what was happening. Yes, I provided an expansion and mentioned the idea of partial function invocation but I never really generalized this idea. So today seems as good a time as any…
In Haskell, when we talk about partial function execution or “currying” we’re referring to the idea that a function with two or more parameters can receive some of it’s parameters now and others later. Take a look at the following function declaration (the declaration is the first line, the definition is the second line):
myFunc :: (Num a) => a -> a -> a myFunc a b = a * b
What this function declaration tells us is that myFunc takes a parameter of type a and a second parameter of type a and returns a result of type a. But there is more going on here. Look at how we define the final returns: -> Why would we use this same symbol between the first and second parameters? The reason is that the definition is telling us something very deep about how Haskell approaches functions – what it’s really saying is that myFunc accepts a parameter of type a and returns a result that is a function. This resulting function accepts a parameter of type a and returns a result of type a.
Let’s rewrite the function declaration to perhaps make this point a bit more clear:
myFunc :: (Num a) => a -> (a -> a) myFunc a b = a * b
we could “curry” or partially execute this function with the following expression:
(myFunc 7)
This expression yields a function that will accept any value and multiply it by 7 – all it needs is that second parameter. Now don’t be fooled into thinking this is a simple parlor trick and that all we’ve really done is put parentheses in an odd place. Haskell allows us to pass this “curried” function as a parameter to any function that accepts a function argument that is required to accept a Num and return a Num.
We can do the same thing with the built in * function:
((*) 7)
Placing the * operator in parentheses like this simply allows us to treat it like a ‘normal’ function with two following parameters. In our expression, we provide it with a value of 7 for the first of these two parameters. When we later use this function by providing it with a Num for its second parameter, it will return that value multiplied by 7.
Again, this isn’t just a shell game – this is how work gets done in Haskell. Consider the map function which takes a function and applies it to every member of a list to generate a new list. The function that it accepts as a parameter is required to consume one parameter and return one result. If we wanted to use map to multiply ever member of a list containing [1,2,3] by 7 then we could simply say:
map ((*) 7) [1,2,3]
this should give us the result [7, 14, 21] (and, in fact, if we try this out in ghci, this is exactly the result that we get).
Now sure we could have written a function that took a number and multiplied it by seven and then passed that function to map but, for a one-off application, sometimes it’s more straightforward to implement it directly like this. And, yes, this example is trivial but it highlights the capabilities that come from Haskell’s partial execution feature.