11

I work in Python. Recently, I discovered a wonderful little package called fn. I've been using it for function composition.

For example, instead of:

baz(bar(foo(x))))

with fn, you can write:

(F() >> foo >> bar >> baz)(x) .

When I saw this, I immediately thought of Clojure:

(-> x foo bar baz) .

But notice how, in Clojure, the input is on the left. I wonder if this possible in python/fn.

Charles R
  • 17,989
  • 6
  • 20
  • 18
  • 3
    While the operator overloading behavior is interesting, to me this just seems like a bad thing to do in real code. – Markus Unterwaditzer Jun 23 '13 at 19:24
  • There's no way to make that exact syntax work in Python, if that's what you're asking. You may be able to approximate it in various ways. What exactly is important to you syntax-wise? – BrenBarn Jun 23 '13 at 19:24
  • Keeping the flow from left to right. Presently, the argument to the composed function is at the end. It would be clearer if I could write F() >> x >> foo >> bar >> baz, or similar. – Charles R Jun 23 '13 at 20:37

7 Answers7

11

You can't replicate the exact syntax, but you can make something similar:

def f(*args):
    result = args[0]

    for func in args[1:]:
        result = func(result)

    return result

Seems to work:

>>> f('a test', reversed, sorted, ''.join)
' aestt'
Blender
  • 289,723
  • 53
  • 439
  • 496
2

You can't get that exact syntax, although you can get something like F(x)(foo, bar, baz). Here's a simple example:

class F(object):
    def __init__(self, arg):
        self.arg = arg

    def __call__(self, *funcs):
        arg = self.arg
        for f in funcs:
            arg = f(arg)
        return arg

def a(x):
    return x+2
def b(x):
    return x**2
def c(x):
    return 3*x

>>> F(2)(a, b, c)
48
>>> F(2)(c, b, a)
38

This is a bit different from Blender's answer since it stores the argument, which can later be re-used with different functions.

This is sort of like the opposite of normal function application: instead of specifying the function up front and leaving some arguments to be specified later, you specify the argument and leave the function(s) to be specified later. It's an interesting toy but it's hard to think why you'd really want this.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
1

If you want to use fn, with a little hack you can get a bit closer to Clojure syntax:

>>> def r(x): return lambda: x
>>> (F() >> r(x) >> foo >> bar >> baz)()

See how I added another function at the beginning of the composition chain that will just return x when called. The problem with this is that you still have to call your composed function, just without any arguments.

I think @Blender's answer is your best bet trying to emulate Clojure's thread function in Python.

MisterMetaphor
  • 5,900
  • 3
  • 24
  • 31
0

I came up with this

def _composition(arg, *funcs_and_args):
    """
    _composition(
        [1,2,3], 
        (filter, lambda x: x % 2 == 1), 
        (map, lambda x: x+3)
    )
    #=> [4, 6]
    """
    for func_and_args in funcs_and_args:
        func, *b = func_and_args
        arg = func(*b, arg)
    return(arg)
fl00r
  • 82,987
  • 33
  • 217
  • 237
0

This seems to work for simple input. Not sure it is worth the effort for complex input, e.g., ((42, 'spam'), {'spam': 42}).

def compose(function, *functions):
    return function if not functions else \
            lambda *args, **kwargs: function(compose(*functions)(*args, **kwargs))

def rcompose(*functions):
    return compose(*reversed(functions))

def postfix(arg, *functions):
    return rcompose(*functions)(arg)

Example:

>>> postfix(1, str, len, hex)
'0x1'
>>> postfix(1, hex, len)
3
Ana Nimbus
  • 635
  • 3
  • 16
0

My compose function that returns a function

def compose(*args):
    length = len(args)
    def _composeInner(lastResult, index):
        if ((length - 1) < index):
            return lastResult
        return _composeInner(args[index](lastResult), index + 1)

    return (lambda x: _composeInner(x, 0))

Usage:

fn = compose(
        lambda x: x * 2,
        lambda x: x + 2,
        lambda x: x + 1,
        lambda x: x / 3
    )

result = fn(6) # -> 5
0

I understand what you mean. It doesn't make sense. In my opinion this python library does it better.

>>> from compositions.compositions import Compose
>>> foo = Compose(lambda x:x)
>>> foo = Compose(lambda x:x**2)
>>> foo = Compose(lambda x:sin(x))
>>> (baz*bar*foo)(x)