Partial Application
Examine this Java expression and anticipate what would happen when compiling and running it in a full program:
Math.min(10)
Consider this similar expression in Haskell:
min 10
The Java version fails to typecheck. The min
method requires two parameters, and only one has been provided. The Haskell version, on the other hand, does typecheck. But how can that be, given its type signature?
> :t min
min :: Ord a => a -> a -> a
Haskell's min
also expects two parameters. Since min 10
typechecks, we can use GHCI to inspect its type:
> :t (min 10)
(min 10) :: (Ord a, Num a) => a -> a
The type of min 10
is a function that expects one parameter. This reveals an interesting feature of Haskell. If we call a function but don't provide all of its parameters, we get back a function waiting on just the remaining parameters. This partial application is another mechanic for defining new functions in Haskell.
The benefit of partial application is that we can define a new, simpler function from an existing, more general one. By preloading the parameters, we produce a function that has a narrower focus and a smaller interface than the original. Sometimes this focus is just a matter of convenience. For example, suppose we wanted to find the non-zero numbers in several different lists. We could call filter
several different times all with the same predicate:
filter (\x -> x /= 0) temperatures
filter (\x -> x /= 0) balances
filter (\x -> x /= 0) biases
With partial application, we may store an intermediate function that has the predicate pre-loaded:
-- Partially apply to make a convenience function.
nonzeroes = filter (\x -> x /= 0)
nonzeroes temperatures
nonzeroes balances
nonzeroes biases
The predicate passed to filter
is retained in the nonzeroes
closure. It doesn't have to be passed in on the three calls.
In other circumstances, the narrower function is more than convenience. It's needed in order to fit the expectations of other functions. The builtin map
, for example, expects a transformation function that accepts only a single parameter: the current list element. Passing the two-parameter min
will not typecheck. But we can pass a partially applied version of min
. This code passes a partially-applied min
to map
in order to clamp each value in a list at 10:
clamp10 :: [Int] -> [Int]
clamp10 xs = map (min 10) xs
numbers = clamp10 [9, 10, 11, 12] -- yields [9, 10, 10, 10]
We can also partially apply binary operators. Earlier we saw this call to filter
:
nonzeroes = filter (\x -> x /= 0)
The lambda is unnecessary because partial application allows us to leave off the element parameter to produce the exact same predicate function:
nonzeroes = filter (/= 0)
We can leave off either the first operand or the second, depending on which parameter we want to leave unapplied. The expression (/ 2)
produces a function that divides the remaining parameter by 2, whereas (2 /)
produces a function that divides 2 by the remaining parameter.
Named operators like div
can also be partially applied. Recall that div 15 3
is in prefix form and 15 `div` 3
is in infix form. The second operand can be omitted in either form. The first operand can only be omitted in infix form.
Learning when to use parentheses takes time. We parenthesize partial-applications of operators or infix functions. We don't parenthesize partial-applications of prefix functions unless the application is nested inside a larger expression.
Point-free Style
Before we knew about partial application, we would have defined a function filtering out the even numbers from a list like this:
evens :: [Int] -> [Int]
evens numbers = filter even numbers
Partial application frees us from having to mention every single parameter when defining a function. In the case of evens
, we can leave off numbers
from both sides of the definition:
evens :: [Int] -> [Int]
evens = filter even
This doesn't change the function or type signature in the least. Partially applying filter
yields a function waiting on a list of Int
. The evens
function therefore expects a list of Int
just as before, but it's now implicit. A function definition with implicit parameters is in point-free style. This style can be applied anywhere that the formal parameter list and the function body end in the same identifiers.
The term point-free was not coined by the Haskell developers. It is borrowed from topology, a branch of mathematics. Topologists deform shapes with functions. When a function's definition doesn't explicitly mention the points of the shape that it transforms, it is point-free.
Currying
Partial application works in Haskell because its designers decided that every function would accept exactly one parameter. A single parameter sounds restrictive if we've spent our programming career writing Python and Java. But it's not in Haskell. Recall that min
has this type signature:
a -> a -> a
We've been interpreting this to mean that min
accepts two parameters of type a
and returns a value of type a
. But adding explicit parentheses leads to an interpretation that better reflects the truth:
a -> (a -> a)
Function min
accepts one parameter and yields a new function awaiting the second. You can pass both parameters right away or just pass one. Your call.
Simulating multiple-parameter functions using single-parameter functions that yield partially-applied functions is currying. This concept was popularized by the 20th century mathematician Haskell Curry. The primary benefit of currying is that it allows us to treat every multiple-parameter function as a generator of convenience functions. We pass in a subset of parameters to get back a simpler function awaiting the remaining.
Few languages beyond Haskell and Scala support automatic currying. However, we could introduce it on our own by writing each function to accept only a single parameter. Any function that needs more parameters returns a new function waiting on the next parameter. For example, we could implement this curriedMin
in JavaScript:
function curriedMin(a) {
return b => Math.min(a, b);
}
Both the normal function and the lambda accept a single parameter. JavaScript doesn't provide a clean syntax for passing multiple parameters at once. We just chain the calls:
console.log(curriedMin(10)(12)); // prints 10
Or we can store the partially-applied function under a meaningful name:
const clamp10 = curriedMin(10);
console.log(clamp10(12)); // prints 10
We can stage our own partial application in any language that supports closures, which are needed to retain the applied parameters, and first-class functions, which are needed to return the function awaiting the remaining parameters.