O'Reilly logo

Real World Haskell by Donald Bruce Stewart, Bryan O'Sullivan, John Goerzen

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Partial Function Application and Currying

You may wonder why the -> arrow is used for what seems to be two purposes in the type signature of a function:

ghci> :type dropWhile
dropWhile :: (a -> Bool) -> [a] -> [a]

It looks like the -> is separating the arguments to dropWhile from each other, but that it also separates the arguments from the return type. Iin fact -> has only one meaning: it denotes a function that takes an argument of the type on the left and returns a value of the type on the right.

The implication here is very important. In Haskell, all functions take only one argument. While dropWhile looks like a function that takes two arguments, it is actually a function of one argument, which returns a function that takes one argument. Here’s a perfectly valid Haskell expression:

ghci> :module +Data.Char
ghci> :type dropWhile isSpace
dropWhile isSpace :: [Char] -> [Char]

Well, that looks useful. The value dropWhile isSpace is a function that strips leading whitespace from a string. How is this useful? As one example, we can use it as an argument to a higher order function:

ghci> map (dropWhile isSpace) [" a","f","   e"]
["a","f","e"]

Every time we supply an argument to a function, we can chop an element off the front of its type signature. Let’s take zip3 as an example to see what we mean; this is a function that zips three lists into a list of three-tuples:

ghci> :type zip3
zip3 :: [a] -> [b] -> [c] -> [(a, b, c)]
ghci> zip3 "foo" "bar" "quux"
[('f','b','q'),('o','a','u'),('o','r','u')]

If we apply zip3 with just one argument, we get a function that accepts two arguments. No matter what arguments we supply to this compound function, its first argument will always be the fixed value we specified:

ghci> :type zip3 "foo"
zip3 "foo" :: [b] -> [c] -> [(Char, b, c)]
ghci> let zip3foo = zip3 "foo"
ghci> :type zip3foo
zip3foo :: [b] -> [c] -> [(Char, b, c)]
ghci> (zip3 "foo") "aaa" "bbb"
[('f','a','b'),('o','a','b'),('o','a','b')]
ghci> zip3foo "aaa" "bbb"
[('f','a','b'),('o','a','b'),('o','a','b')]
ghci> zip3foo [1,2,3] [True,False,True]
[('f',1,True),('o',2,False),('o',3,True)]

When we pass fewer arguments to a function than the function can accept, we call it partial application of the function—we’re applying the function to only some of its arguments.

In the previous example, we have a partially applied function, zip3 "foo", and a new function, zip3foo. We can see that the type signatures of the two and their behavior are identical.

This applies just as well if we fix two arguments, giving us a function of just one argument:

ghci> let zip3foobar = zip3 "foo" "bar"
ghci> :type zip3foobar
zip3foobar :: [c] -> [(Char, Char, c)]
ghci> zip3foobar "quux"
[('f','b','q'),('o','a','u'),('o','r','u')]
ghci> zip3foobar [1,2]
[('f','b',1),('o','a',2)]

Partial function application lets us avoid writing tiresome throwaway functions. It’s often more useful for this purpose than the anonymous functions we introduced earlier in this chapter in Anonymous (lambda) Functions. Looking back at the isInAny function we defined there, here’s how we’d use a partially applied function instead of a named helper function or a lambda:

-- file: ch04/Partial.hs
isInAny3 needle haystack = any (isInfixOf needle) haystack

Here, the expression isInfixOf needle is the partially applied function. We’re taking the function isInfixOf and fixing its first argument to be the needle variable from our parameter list. This gives us a partially applied function that has exactly the same type and behavior as the helper and lambda in our earlier definitions.

Partial function application is named currying, after the logician Haskell Curry (for whom the Haskell language is named).

As another example of currying in use, let’s return to the list-summing function we wrote in The Left Fold:

-- file: ch04/Sum.hs
niceSum :: [Integer] -> Integer
niceSum xs = foldl (+) 0 xs

We don’t need to fully apply foldl; we can omit the list xs from both the parameter list and the parameters to foldl, and we’ll end up with a more compact function that has the same type:

-- file: ch04/Sum.hs
nicerSum :: [Integer] -> Integer
nicerSum = foldl (+) 0

Sections

Haskell provides a handy notational shortcut to let us write a partially applied function in infix style. If we enclose an operator in parentheses, we can supply its left or right argument inside the parentheses to get a partially applied function. This kind of partial application is called a section:

ghci> (1+) 2
3
ghci> map (*3) [24,36]
[72,108]
ghci> map (2^) [3,5,7,9]
[8,32,128,512]

If we provide the left argument inside the section, then calling the resulting function with one argument supplies the operator’s right argument, and vice versa.

Recall that we can wrap a function name in backquotes to use it as an infix operator. This lets us use sections with functions:

ghci> :type (`elem` ['a'..'z'])
(`elem` ['a'..'z']) :: Char -> Bool

The preceding definition fixes elem’s second argument, giving us a function that checks to see whether its argument is a lowercase letter:

ghci> (`elem` ['a'..'z']) 'f'
True

Using this as an argument to all, we get a function that checks an entire string to see if it’s all lowercase:

ghci> all (`elem` ['a'..'z']) "Frobozz"
False

If we use this style, we can further improve the readability of our earlier isInAny3 function:

-- file: ch04/Partial.hs
isInAny4 needle haystack = any (needle `isInfixOf`) haystack

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required