Pythonic function composition

Discussion in 'Python' started by Alan G Isaac, Oct 25, 2004.

  1. Alan G Isaac

    Alan G Isaac Guest

    Given a list of functions, it seems there must be a
    Pythonic approach to composition. Something like

    def compose(fns): return lambda x: reduce(lambda f,g: f(g),fns)(x)

    This will not work because the argument 'x' is not "inside".
    What is the proper formulation?

    Thanks,
    Alan Isaac
     
    Alan G Isaac, Oct 25, 2004
    #1
    1. Advertising

  2. Alan G Isaac

    Peter Otten Guest

    Alan G Isaac wrote:

    > Given a list of functions, it seems there must be a
    > Pythonic approach to composition. Something like
    >
    > def compose(fns): return lambda x: reduce(lambda f,g: f(g),fns)(x)
    >
    > This will not work because the argument 'x' is not "inside".
    > What is the proper formulation?


    You need to pass a function that makes a function (a "factory") to reduce():

    >>> fns = [lambda x: x+2, lambda x: x*2, lambda x: x*x]
    >>> def compose(f, g):

    .... def fog(x):
    .... return f(g(x))
    .... return fog
    ....
    >>> g1 = reduce(compose, fns)
    >>> g1(2)

    10

    The same with lambdas:

    >>> g2 = reduce(lambda f, g: lambda x: f(g(x)), fns)
    >>> g2(2)

    10

    Peter
     
    Peter Otten, Oct 25, 2004
    #2
    1. Advertising

  3. Alan G Isaac <> wrote:
    > Given a list of functions, it seems there must be a
    > Pythonic approach to composition. Something like
    >
    > def compose(fns): return lambda x: reduce(lambda f,g: f(g),fns)(x)
    >
    > This will not work because the argument 'x' is not "inside".
    > What is the proper formulation?


    There are probably several ways to do it.
    The following is the one which come to my mind first.

    The trick is to first define a function that composes
    two functions, and then use reduce() to apply it to an
    arbitrary number of functions.

    >>> def compose2 (f, g):

    .... def h (x):
    .... return f(g(x))
    .... return h
    ....
    >>> def compose (fns):

    .... return reduce(compose2, fns)
    ....

    Some testing:

    >>> def add42 (x): return x + 42

    ....
    >>> def mul2 (x): return x * 2

    ....
    >>> def sub5 (x): return x - 5

    ....
    >>> def div3 (x): return x / 3

    ....
    >>> a = compose((add42, mul2, sub5, div3))
    >>> a(1)

    27

    There's no need to juggle with lambda in this case.
    Lambda has its uses, but this isn't one of them.

    Best regards
    Oliver

    --
    Oliver Fromme, Konrad-Celtis-Str. 72, 81369 Munich, Germany

    ``All that we see or seem is just a dream within a dream.''
    (E. A. Poe)
     
    Oliver Fromme, Oct 25, 2004
    #3
  4. Alan G Isaac

    Alan G Isaac Guest

    "Peter Otten" <> wrote in message
    news:clj5nq$jl$02$-online.com...
    > >>> g2 = reduce(lambda f, g: lambda x: f(g(x)), fns)



    Cool. That gets me there. I think
    def compose(fns) : return reduce(lambda f, g: lambda x: f(g(x)), fns)
    does exactly what I want.

    Thanks,
    Alan Isaac
     
    Alan G Isaac, Oct 25, 2004
    #4
  5. Alan G Isaac

    Alan G Isaac Guest

    "Oliver Fromme" <> wrote in message
    news:...
    > There's no need to juggle with lambda in this case.
    > Lambda has its uses, but this isn't one of them.


    I kind of like the lambda version (see Peter's post).
    But maybe it is more opaque.

    Thanks,
    Alan
     
    Alan G Isaac, Oct 25, 2004
    #5
  6. In article <>,
    "Alan G Isaac" <> wrote:

    > Given a list of functions, it seems there must be a
    > Pythonic approach to composition. Something like
    >
    > def compose(fns): return lambda x: reduce(lambda f,g: f(g),fns)(x)
    >
    > This will not work because the argument 'x' is not "inside".
    > What is the proper formulation?
    >


    If you are only concerned with unary functions, then composition is
    fairly trivial to deal with:

    def compose(*fns):
    def id(x): return x

    def c2(f, g):
    def h(x): return f(g(x))
    return h

    return reduce(c2, fns, id)

    However, if you want to deal with functions that may take multiple
    arguments, you must be a little more clever. Here's one way that seems
    to work okay:

    def compose(*fns):
    def id(*args): return args

    def box(res):
    if isinstance(res, (list, tuple)):
    return res
    else:
    return (res,)

    def unbox(res):
    if isinstance(res, (list, tuple)) and len(res) == 1:
    return res[0]
    else:
    return res

    def c2(f, g):
    def h(*args):
    return unbox(f(*box(g(*args))))
    return h

    return reduce(c2, fns, id)

    For instance:
    def f1(a, b):
    return (a / b, a % b)

    def f2(a, b):
    return a + b

    def f3(a):
    return a + 2

    h = compose(f3, f2, f1)
    h(5, 3)
    ==> 5

    This will work, but it's not the most efficient possible solution. You
    could defer unboxing until the end by defining another intermediate
    function.

    Cheers,
    -M

    --
    Michael J. Fromberger | Lecturer, Dept. of Computer Science
    http://www.dartmouth.edu/~sting/ | Dartmouth College, Hanover, NH, USA
     
    Michael J. Fromberger, Oct 25, 2004
    #6
  7. How about some recursion?

    def compose(f, *fns):
    if not fns:
    return f
    else:
    return lambda x: f(compose(*fns)(x))

    ....

    >>> from math import *
    >>> foo = compose(sin, sqrt, abs) # sin(sqrt(abs(x)))
    >>> foo(-((pi/2.)**2))

    1.0

    ....or you could try it this way, which makes some assumptions about
    function names, but will possibly run faster:

    def compose(*fns):
    fnames = [f.__name__ for f in fns]
    expr = "%s(x%s" % ('('.join(fnames),')'*len(fns))
    return eval("lambda x: %s" % expr)
     
    Lonnie Princehouse, Oct 25, 2004
    #7
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Carl J. Van Arsdall
    Replies:
    4
    Views:
    500
    Bruno Desthuilliers
    Feb 7, 2006
  2. Kay Schluehr
    Replies:
    10
    Views:
    562
    Kay Schluehr
    Feb 4, 2008
  3. Arnaud Delobelle

    An idea for fast function composition

    Arnaud Delobelle, Feb 16, 2008, in forum: Python
    Replies:
    4
    Views:
    331
  4. Edgardo Hames

    Defining a new function by composition

    Edgardo Hames, Aug 6, 2004, in forum: Ruby
    Replies:
    7
    Views:
    124
    Robert Klemme
    Aug 7, 2004
  5. Tom Moertel

    Function composition in Ruby

    Tom Moertel, Apr 7, 2006, in forum: Ruby
    Replies:
    2
    Views:
    84
    Tom Moertel
    Apr 7, 2006
Loading...

Share This Page