Friday, March 4, 2011

Partial Function Application and Function Composition

This following function demonstrates one of my favourite concepts in functional programming -- that of partial function application:

def pfa(f, *p):
    return lambda *q: f(*(p + q))

Partial function application is where you fix particular parameters of a function, producing a new function with the fixed parameters substituted in. For example, say I had a function add(a,b) = a + b. I can use partial function to 'fix' the first argument, a, to a particular number x, and this produces a new function add(b) = x + b.

In practice, PFA is often a more compact and readable version of the ugly anonymous functions you give to higher-order functions such as lambda. For example, consider the following code extract which transforms [a,b,c,...] to [2^a, 2^b, 2^c, ...]:

map(lambda n: pow(2, n), list)

Now take a look at how it is done with PFA:

map(PFA(pow, 2), list)

PFA in this instance creates a new function by substituting the value '2' as the first argument of the pow function, transforming pow(b, e) = b^e into pow(e) = 2^e.

Haskell's PFA notation is even more elegant; because everything in Haskell is just chained evaluation of partial functions, there's no special or unique notation for it, so the following is valid code:

map (2^) list

That maps the partial function 2^ onto list. Haskell also has lambdas, but they're no where near as short or understandable.

map (\x -> 2^x) list

The Python implementation above can also fix multiple arguments at one time, as in pfa(f(a,b,c), a, b) -> f(c). Sometimes it may be preferable to fix the second or third argument, while leaving the first argument variable -- Haskell deals with this elegantly as usual, but I haven't found a nice way to do it in Python.

note: apparently the functools module already has a partial() function for PFA. whoops :)

Function composition is another concept (again, originally mathematical) with similar roots. Here's an implementation:

def compose(*fs):
    return lambda x: reduce(lambda a,b: b(a), (fs+x)[::-1])


Essentially, this takes two functions f(x) and g(y) and produces a new function h(y) == f(g(y)). The new function is often notated as (f.g)(y), as in "f.g is the composition of f and g." Like partial function application, it is rarely a necessity to use function composition, but again it can make things more compact and more intuitive.

A common construct I use when dealing with strings is as follows:

new_s = old_s.strip().split()

This can be composed as follows:

new_s = compose(str.split, str.strip)(old_s)

A better example:

hash(bin(bool(id(sum(range(10))))))
# becomes
compose(hash, bin, bool, id, sum, range)(10)