January 2013

While I don’t consider myself a functional programming guru, all those hours spent in Haskell, Lisp and Scheme definitively changed my way of programming. So, after seeing a lot of unnecessarily complex implementations of function composition in Python on the Web, I decided to write this article to present a simple yet powerful solution that covers all use cases. If you are familiar with function composition, you may want to go to the solution.

Function composition is a way of combining functions such that the result of each function is passed as the argument of the next function. For example, the composition of two functions `f`

and `g`

is denoted `f(g(x))`

. `x`

is the argument of `g`

, the result of `g`

is passed as the argument of `f`

and the result of the composition is the result of `f`

.

Let’s define `compose2`

, a function that takes two functions as arguments (`f`

and `g`

) and returns a function representing their composition:

```
def compose2(f, g):
return lambda x: f(g(x))
```

Example:

```
>>> def double(x):
... return x * 2
...
>>> def inc(x):
... return x + 1
...
>>> inc_and_double = compose2(double, inc)
>>> inc_and_double(10)
22
```

Now that we know how to compose two functions, it would be interesting to generalize it to accept *n* functions. Since the solution is based on `compose2`

, let’s first look at the composition of three functions using `compose2`

.

```
>>> def dec(x):
... return x - 1
...
>>> inc_double_and_dec = compose2(compose2(dec, double), inc)
>>> inc_double_and_dec(10)
21
```

Do you see the pattern? First, we compose the first two functions, then we compose the newly created function with the next one and so on.

Let’s write this in Python.

```
import functools
def compose(*functions):
def compose2(f, g):
return lambda x: f(g(x))
return functools.reduce(compose2, functions)
```

Or in a more compact way:

```
def compose(*functions):
return functools.reduce(lambda f, g: lambda x: f(g(x)), functions)
```

Example:

```
>>> inc_double_and_dec = compose(dec, double, inc)
>>> inc_double_and_dec(10)
21
```

Note that functools.reduce is also called fold.

The reason why implementations get complex is because they support multiple-argument functions. But there is no need to do so, because any function can be transformed to a one-argument function using higher-order functions such as functools.partial, decorators or our own functions.

Examples:

```
>>> def second(*args):
... return args[1]
...
>>> def second_wrapper(lst):
... return second(*lst)
...
>>> pipeline = compose(second_wrapper, list, range)
>>> pipeline(5)
1
```

```
>>> def sub(a, b):
... return a - b
...
>>> pipeline = compose(functools.partial(sub, b=4), operator.neg)
>>> pipeline(-6)
2
```

If you want to learn about functional programming in Python, I recommend this document.

comments powered by Disqus