kachayev / fn.py

Functional programming in Python: implementation of missing features to enjoy FP
Other
3.35k stars 203 forks source link

Function Composition #32

Open creese opened 11 years ago

creese commented 11 years ago

Often, when I chain functions, I find myself using the following pattern:

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

If this were clojure, I could write:

(-> x foo bar baz)

Notice the input on the left. Is there any to way to do this in python/fn?

kachayev commented 11 years ago

Unfortunately, -> is invalid variable name in python, so you can't do anything that looks like clojure thread macros -> and ->>. By the way, it's easy to read only in s-expression notation. But definitely, we can have something like

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

F is not good name for such use case, pipe is more appropriate, I think.

pipe(x) >> foo >> bar >> baz

The only one problem that I see, is that python is strictly evaluated and we need to decide on each reducing step what to do: keep going with new functions or evaluate final result. Possible, we need something like "sentinel" function to execute whole pipe:

pipe(x) >> foo >> bar >> baz >> end

or

pipe(x) >> foo >> bar >> baz >> result

or even

pipe.start(x) >> foo >> bar >> baz >> pipe.end

What do you think about such syntax? Can you give a shining example where it's useful?

creese commented 11 years ago

A lot of my methods tend to look like:

def func(x):
    f = F() >> foo >> bar >> baz
    return f(x)

Lately, I've begun to work with just functions. It feels more Pythonic.

def func(x):
    f = compose(foo, bar, baz)
    return f(x)

or even,

def func(x):
    return thread_last(x, foo, bar, baz)
microamp commented 10 years ago

creese, where is the above compose function from?

By the way, how is this little sugar for pipe/compose? (I will call it pipe for now.)

from fn.func import F
from fn.iters import first, rest

def pipe(*funcs):
    def _pipe(*args, **kwargs):
        return reduce(lambda f, g: f >> g,
                      rest(funcs),
                      F(first(funcs), *args, **kwargs))()

    return _pipe

Some examples to follow

from functools import partial
from operator import add, mul

inc = partial(add, 1)
double = partial(mul, 2)
triple = partial(mul, 3)

double_triple_inc = pipe(double, triple, inc)
inc_double_triple = pipe(inc, double, triple)

assert double_triple_inc(2) == 13  # 2 * 2 * 3 + 1 = 13
assert double_triple_inc(3) == 19  # 3 * 2 * 3 + 1 = 19
assert inc_double_triple(3) == 24  # (3 + 1) * 2 * 3 = 24

and

from functools import partial
from operator import mul, neg
from random import random

rndm = pipe(random, partial(mul, 10), int, neg)
print([rndm() for i in range(10)])  # some random negative integers

Any thoughts?

EDIT: Fix neg missing from the import statement.

venuatu commented 10 years ago

Earlier today I had the crazy idea to do something like this by abusing some operators (or and invert):

mod_primes = ~(pipe(primes)
    | filter(lambda a: a > 20)
    | map(lambda a: a * a)
    | zip_index
)

A simple implementation is at: https://gist.github.com/venuatu/0b35e331aeea9f3feb8f

Would this be a good addition for this library?

iddan commented 7 years ago

Your implementation refers to lists but it actually can work for any type, does'nt it?