Sleeping Cyborg

Jonathan David Page talks about whatever he happens to be thinking about. Sometimes other people join in.

Email · @parathetic (Twitter) · @jdpage (Github)
Subscribe to feed
 

Links

A collection of cool people and projects.

A Simple(ish) Explanation of Haskell Function Signature Madness

by on 22 August 2012
in , ,
with some comments, maybe.

A clarification regarding my use of the word "simple(ish)": I am assuming, firstly, that you are already comfortable with programming in an imperative language such as C, Java, or Python, and secondly, that you already know at least a little Haskell.

Haskell type signatures, to the uninitiated, are a little odd. Take the following simple two-argument function:

plus :: Int -> Int -> Int
plus a b = a + b

If you're coming from an imperative language, you might be tempted to read that signature as "this function takes an Int and an Int and returns an Int". That'll do in most cases, but it isn't really true. And the fact that it isn't true is a really cool feature of Haskell.

The little -> arrow is a right-associative operator. So you whould read Int -> Int -> Int as Int -> (Int -> Int), which doesn't help anything at all, because now it looks even worse. And now here is the kicker: all functions in Haskell take exactly one argument. One. Even our plus function there. Which is odd, because it sure looks like it takes two arguments. The thing is, Haskell does a little magic trick for us (which isn't really magic). This magic is called currying.

The trick is that when you do plus 2 3, two things happen. First, 2 is applied to plus. Application is a fancy way of saying that an argument is given to a function. This application results in a new function, which also takes one argument. 3 is applied to that function, which one might notate as (plus 2), and returns an Int, 5.

In short, plus 2 3 is the same as (plus 2) 3. So the Int -> (Int -> Int) means that plus is a function which takes an Int, returning a function which takes an Int, returning an Int.

So why is that useful? Well, consider the builtin function map, which has the signature:

map :: (a -> b) -> [a] -> [b]

Basically, it takes a function which takes a thing of type a and returns a thing of type b, and an array of things of type a. It then spits out an array of things of type b, which is generated by applying the function to every argument the array of things of type a.

So map (\b -> plus 2 b) [3 1 4 1 6] returns [5 3 6 3 8].

Now, remember that (plus 2) returns a function, right? So you could also do:

map (plus 2) [3 1 4 1 6]

and get the same result.

tl;dr: Calling a Haskell function with not enough arguments basically returns what you might think of as a half-called function. It's got some arguments already, you just need to supply the remaining ones. And it's just another function.