Question

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.

Was it helpful?

Solution

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'

OTHER TIPS

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.

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.

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)

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

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

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)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top