One of the exercises in Introduction to Functional Programming by Richard Bird and Philip Wadler is to work out the type signature of map map
(i.e. calling map
with map
as its first argument). I’ve generally struggled to deal with all but the simplest of partial function application, but I found a great thread on the Lambda the Ultimate forums that really helped me out with this. Two commenters suggested different approaches: going through the maths, and understanding the abstraction.
Solving algebraically
The signature for is:
The first argument is a function which takes an and returns a . The second argument is a list of . The last type, a list of , is the function’s return value.
To work out the signature of , we need to substitute in what we know about ’s type signature to work out the resulting combination of and . I found this really tricky, but by applying a few rules we can work it out.
First, let’s pass some arbitrary function as the first argument to .
In our case, we want to pass as . But is a function that takes one argument, while takes two. So how can we use it in place of ? Well, the operator is right-associative, which means we can re-write like this:
This is know as currying. In Haskell, all functions can be considered to take one argument, and return a function that takes the remainder of the arguments. Here, we’re thinking of as a function which takes a function from , and returns a function that takes a and returns a .
Now we have in a form that matches :
So:
Now we can substitute back into our type declaration:
So takes a list of functions of the form and returns a list of functions that take an returns (i.e. ).
Solving intuitively
The other suggested approach was to think about the abstractions being used, rather than the mathematical basis for the functions. I tend to struggle with this, but it is worth trying to come to grips with the concepts and intention, and not just rely on somewhat-blindly applying maths principles.
The function conceptually represents transforming a function to work on a list. For example, (+1)
adds one to a single number, while map (+1)
adds on to a list of numbers. This corresponds to the step in our last approach where we starting thinking of as .
Continuing this line of thinking, will then take a list of functions and return a list of transformed functions that work on lists of , or . This gives us the same result as last time, .
I tend to get lost when I try to think about it this way, but hopefully I’ll start to get the hang of reasoning about functions this way as I get more practice. If not, there is always the option of falling back on the maths.