Partial Application

Dear Computer

Chapter 8: Functions Revisited

Partial Application

Examine this Java expression and anticipate what would happen when compiling and running it in a full program:

Java
Math.min(10)

Consider this similar expression in Haskell:

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?

GHCI
> :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:

GHCI
> :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:

Haskell
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:

Haskell
-- 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:

Haskell
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:

Haskell
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:

Haskell
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:

Haskell
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:

Haskell
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:

Haskell
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:

Haskell
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:

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:

JavaScript
console.log(curriedMin(10)(12));  // prints 10

Or we can store the partially-applied function under a meaningful name:

JavaScript
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.

← ClosuresComposition →