0

I'm trying to translate my Javascript compose functions into Python but I'm having trouble with the last one. How can I generalize an unknown number of functions?

After reading this, I would like to avoid the reduce() method.

javascript:

/* compose functions */
const comp1 = (fn2, fn1) => arr       => fn2(fn1(arr))                            // 2 functions, one array
const comp2 = (fn2, fn1) => (...arrs) => fn2(fn1(...arrs))                        // 2 functions, multiple arrays
const comp3 = (...fns)   => (...arrs) => fns.reduceRight((v,f) => f(v), arrs)     // multiple functions, multiple arrays

python:

/* compose functions */
comp1 = lambda fn2,fn1: lambda arr:   fn2(fn1(arr))   # 2 functions, 1 array
comp2 = lambda fn2,fn1: lambda *arrs: fn2(fn1(*arrs)) # 2 functions, multiple arrays
comp3 = lambda *fns:    lambda *arrs: ????

all improvements are appreciated...

swyveu
  • 135
  • 8
  • 1
    Why exactly do you want to avoid `reduce`? – Stefan Pochmann Feb 17 '18 at 19:23
  • Possible duplicate of [Composing functions in python](https://stackoverflow.com/questions/16739290/composing-functions-in-python) – wwii Feb 17 '18 at 19:24
  • It seems our dear Guido is not too fond of the `reduce()` method. As mentioned in the provided article, (https://www.artima.com/weblogs/viewpost.jsp?thread=98196), `reduce()` is moved to functools in Python3. I would like to avoid imports. Also, if this method is avoided, I would expect an alterative which is similarly concise. I was hoping someone here could provide it... – swyveu Feb 17 '18 at 19:38
  • 1
    Ok then the question is why would you like to avoid imports. – Stefan Pochmann Feb 17 '18 at 19:44
  • The reason we tend not to use reduce in Python is because it does not have a real good syntax for functional programming (lambdas have a heavy notation). But here since you specifically want to do functional programming, there is no reason not to use it. – Olivier Melançon Feb 17 '18 at 19:48
  • Hey, I thought I was asking the questions here :). I have nothing against `reduce()` nor against imports. But after reading the article (try it), and specifically reading this: "the planned demise of reduce() and lambda in Python 3000", I'm fishing for alternatives... – swyveu Feb 17 '18 at 20:03
  • Instead of using implicit use explicit recursion: `const compN = (f, ...fs) => (...args) => f === undefined ? args[0] : compN(...fs) (f(...args))`. However, I don't know if python has rest/spread syntax. –  Feb 17 '18 at 20:53

2 Answers2

1

If you really do not want to use reduce, which I believe would be the correct approach, you can have a loop. Althought, this prevents you from using lambda.

def compose(*fns):

    def composed(*args, **kwargs):
        output = fns[-1](*args, **kwargs)
        for fn in reversed(fns[:-1]):
            output = fn(output)

        return output

    return composed

I want to state again that there is no reason not to use reduce, as the above is simply a specific implementation of reduce. Composition is a reduction.

from functools import reduce # Only required in Python3 where reduce was moved

def compose(f1, f2):
    return lambda *args, **kwargs: f1(f2(*args, **kwargs))

def compose_many(*fns):
    return reduce(compose, fns, lambda x: x)
Olivier Melançon
  • 21,584
  • 4
  • 41
  • 73
1

Here's a nice variadic compose to facilitate right-to-left function composition – it works with 0, 1, or more functions

def identity (x):
  return x

def compose (f = identity, *gs):
  if not gs:
    return f
  else:
    return lambda x: f (compose (*gs) (x))

def double (x):
  return x * 2

def add1 (x):
  return x + 1


# identity (5)    
compose () (5)                            # 5

# print (5)
compose (print) (5)                       # 5

# print (add1 (5))
compose (print, add1) (5)                 # 6

# print (add1 (double (5)))
compose (print, add1, double) (5)         # 11

# print (add1 (double (double (5))))
compose (print, add1, double, double) (5) # 21

To make it support variadic arguments, just update the lambda – changes in bold

def identity (x):
  return x

def compose (f = identity, *gs):
  if not gs:
    return f
  else:
    return lambda *xs: f (compose (*gs) (*xs))

def mult (x,y):
  return x * y

def double (x):
  return mult (2, x)

# print (double (double (mult (2, 3))))
compose (print, double, double, mult) (2,3) # 24
Mulan
  • 129,518
  • 31
  • 228
  • 259