Part 17

Partial Application, Prefix and Infix

When using higher-order functions you can find yourself defining lots of small helper functions, like addThree or shorten in the previous examples. This is a bit of a chore in the long run, but luckily Haskell’s functions behave a bit weirdly…

Let’s start in GHCi:

Prelude> add a b = a+b
Prelude> add 1 5
6
Prelude> addThree = add 3
Prelude> addThree 2
5

So, we’ve defined add, a function of two arguments, and only given it one argument. The result is not a type error but a new function. The new function just stores (or remembers) the given argument, waits for another argument, and then gives both to add.

Prelude> map addThree [1,2,3]
[4,5,6]
Prelude> map (add 3) [1,2,3]
[4,5,6]

Here we can see that we don’t even need to give a name to the function returned by add 3. We can just use it anywhere where a function of one argument is expected.

This is called partial application. All functions in Haskell behave like this. Let’s have a closer look. Here’s a function that takes many arguments.

between :: Integer -> Integer -> Integer -> Bool
between lo high x = x < high && x > lo

Prelude> between 3 7 5
True
Prelude> between 3 6 8
False

We can give between less arguments and get back new functions, just like we saw with add:

Prelude> (between 1 5) 2
True
Prelude> let f = between 1 5 in f 2
True
Prelude> map (between 1 3) [1,2,3]
[False,True,False]

Look at the types of partially applying between. They behave neatly, with arguments disappearing one by one from the type as values are added to the expression.

Prelude> :t between
between :: Integer -> Integer -> Integer -> Bool
Prelude> :t between 1
between 1 :: Integer -> Integer -> Bool
Prelude> :t between 1 2
between 1 2 :: Integer -> Bool
Prelude> :t between 1 2 3
between 1 2 3 :: Bool

Actually, when we write a type like Integer -> Integer -> Integer -> Bool, it means Integer -> (Integer -> (Integer -> Bool)). That is, a multi-argument function is just a function that returns a function. Similarly, an expression like between 1 2 3 is the same as ((between 1) 2) 3, so passing multiple arguments to a function happens via multiple single-argument calls. Representing multi-argument functions like this is called currying (after the logician Haskell Curry). Currying is what makes partial application possible.

Here’s another example of using partial application with map:

map (drop 1) ["Hello","World!"]
    ==> ["ello","orld!"]

In addition to normal functions, partial application also works with operators. With operators you can choose whether you apply the left or the right argument. (Partially applied operators are also called sections or operator sections). Some examples:

Prelude> map (*2) [1,2,3]
[2,4,6]
Prelude> map (2*) [1,2,3]
[2,4,6]
Prelude> map (1/) [1,2,3,4,5]
[1.0,0.5,0.3333333333333333,0.25,0.2]

Prefix and Infix Notations

Normal Haskell operators are applied with prefix notation, which is just a fancy way to say that the function name comes before the arguments. In contrast, operators are applied with infix notation – the name of the function comes between the arguments.

An infix operator can be converted into a prefix function by adding parentheses around it. For instance,

    (+) 1 2 ==> 1 + 2 ==> 3

This is useful especially when an operator needs to be passed as an argument to another function.

As an example, the function zipWith takes two lists, a binary function, and joins the lists using the function. We can use zipWith (+) to sum two lists, element-by-element:

Prelude> :t zipWith
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
Prelude> zipWith (+) [0,2,5] [1,3,3]
[1,5,8]

Without the ability to turn an operator into a function, we’d have to use a helper function – such as add above.

Note that omitting the parentheses leads into a type error:

Prelude> zipWith + [0,2,5,3] [1,3,3]

<interactive>:1:11: error:Couldn't match expected type[Integer]
                                    -> (a -> b -> c) -> [a] -> [b] -> [c]with actual type[Integer]’
    • The function[0, 2, 5, 3]is applied to one argument,
        but its type[Integer]has none
        In the second argument of(+), namely[0, 2, 5, 3] [1, 3, 3]In the expression: zipWith + [0, 2, 5, 3] [1, 3, 3]Relevant bindings include
        it :: (a -> b -> c) -> [a] -> [b] -> [c]
            (bound at <interactive>:1:1)

The reason for this weird-looking error is that GHCi got confused and thought that we were somehow trying to add zipWith and [0,2,5,3] [1,3,3] together. Logically, it deduced that [0,2,5,3] must be a function since it’s being applied to [1,3,3] (remember that functions bind tighter than operators).

Unfortunately, error messages can sometimes be obscure, since the compiler cannot always know the “real” cause of the error (which is in this case was omitting the parentheses). Weird error messages are frustrating, but only the programmer knows what was the original intent behind the code.

Another nice feature of Haskell is the syntax for applying a binary function as if it was an infix operator, by surrounding it with backticks (`). For example:

6 `div` 2 ==> div 6 2 ==> 3
(+1) `map` [1,2,3] ==> map (+1) [1,2,3] ==> [2,3,4]
You have reached the end of this section! Continue to the next section:

You can check your current points from the blue blob in the bottom-right corner of the page.