[Python-Dev] functools.compose to chain functions together

Steven D'Aprano steve at pearwood.info
Mon Aug 17 09:43:52 CEST 2009


On Mon, 17 Aug 2009 08:10:16 am Martin v. Löwis wrote:

> I don't think he did. Comparing it to the one obvious solution (use
> a lambda expression), his only reasoning was "it is much easier to 
> read". I truly cannot believe that a compose function would be
> easier to read to the average Python programmer: if you have
>
>   def foo(data):
>     return compose(a, b(data), c)
>
> what would you expect that to mean? 

foo is a factory function that, given an argument `data`, generates a 
function b(data), then composes it with two other functions a and c, 
and returns the result, also a function.


> Please rewrite it as a regular 
> Python expression, preferably without looking at the patch that
> has been proposed first. I bet there is a 50% chance that you get 
> it wrong (because there are two possible interpretations).

But surely only one of them will agree with the standard definition of 
function composition. Both Mathworld and Wikipedia agree that f∘g(x) 
is equivalent to f(g(x)):

http://mathworld.wolfram.com/Composition.html
http://en.wikipedia.org/wiki/Function_composition

and I don't see any reason why a compose() function shouldn't do the 
same.


(Aside: how do I look at the patch? The only link I have is here:
http://mail.python.org/pipermail/patches/2007-February/021687.html
but I can't see how to get to the patch from there.)


foo could be written as:

def foo(data):
    return lambda *args, **kwargs: a(b(data)(c(*args, **kwargs)))

Or without lambda:

def foo(data):
    def composed(*args, **kwargs):
        return a(b(data)(c(*args, **kwargs)))
    return composed

This soon gets unwieldy:

def foo(arg1, arg2, arg3):
    return compose(
      f, g, h, factory(arg1), factory(arg2), factory(arg3)
    )

versus 

def foo(arg1, arg2, arg3):
    return lambda *a, **kw: (
      f(g(h(factory(arg1)(factory(arg2)(factory(arg3)(*a, **kw))))))
    )

but presumably composing six functions is rare.

A further advantage of compose() is that one could, if desired, 
generate a sensible name and doc string for the returned function. 
Depends on how heavyweight you want compose() to become.

I think the compose() version is far more readable and understandable, 
but another factor is the performance cost of the generated function 
compared to a hand-made lambda.



For the record, Haskell makes compose a built-in operator:

http://www.haskell.org/haskellwiki/Function_composition

It doesn't appear to be standard in Ruby, but it seems to be commonly 
requested, and a version is on Facets:

http://facets.rubyforge.org/apidoc/api/core/classes/Proc.html#M000161



-- 
Steven D'Aprano 


More information about the Python-Dev mailing list