4

I'm slowly trying to get into functional programming in Python and came across the following problem:

Given two functions f1 and f2, how can I construct a function f that multiplies these two functions with the same argument 'in a functional way'?

Not having dived too much into functional programming I do have one solution

f = lambda x : f1(x) * f2(x)

but it somehow didn't seem to be in the right spirit of functional programming.

My next attempt was to use the mul and the juxt operators like this

>>> from tools import juxt
>>> from operator import mul
>>> f = mul(juxt(f1,f2))
TypeError: op_mul expected 2 arguments, got 1

Trying to split the tuple output of juxt with * didn't work either:

>>> f = mul(*juxt(f1, f2))
TypeError: mul() argument after * must be an iterable, not juxt

Using lambda again seemed to work, but it somehow defeated the whole purpose...

>>> temp  = juxt(f_1, f_2)
>>> f = lambda x : mul(*temp(x))

Maybe I'm being too pedantic or unthankful (to Python) here, but I feel like I'm missing something very essential or routine in functional programming.

Is there a more functional way of doing this?

chickenNinja123
  • 311
  • 2
  • 11
  • 1
    I don't understand what you are asking. For starters, you should not be assigning `lambda` expressions to a name, that defeats their entire purpose. Just use a full function definition. But what isn't "in the true spirit of functional program"? what is wrong with `f = lambda x : f1(x) * f2(x)` (aside from using a lambda to define a named function) – juanpa.arrivillaga Dec 04 '18 at 18:39
  • It seems in your second and third examples, you have written the same line `mul(juxt(f1, f2))` when I think you meant to write something different in the third one – Jordan Epstein Dec 04 '18 at 18:41
  • Thanks @Jordan Epstein, I just fixed that – chickenNinja123 Dec 04 '18 at 18:45
  • @juanpa.arrivillaga, I thought `lambda` is just a short version of `def`, so I don't see why I shouldn't (in general) do that. But yeah, I myself don't know exactly what I mean by 'in a functional way', but maybe the fact that `lambda` is evaluated point wise kinda bothered me. I hoped that people with a background in languages like Huskell and Lisp maybe have some insights or at least something to say to that. – chickenNinja123 Dec 04 '18 at 18:48
  • 3
    No problem. I do not use functional programming often but I will say that your first solution is probably the most pythonic and common way to solve your problem. – Jordan Epstein Dec 04 '18 at 18:49
  • 1
    @chickenNinja123 it is explicitly against PEP8 to assign a lambda expression to a name. That shouldn't necessarily stop you, but do realize that. But what do you mean "evaluated pointwise"? – juanpa.arrivillaga Dec 04 '18 at 19:00
  • I mean that I have to explicitly define this function (the multiplication function) and cannot express it in terms of composing already existing elementary functions. The question seems so basic that it feels wrong having to define it „from scratch“ – chickenNinja123 Dec 04 '18 at 20:20
  • 1
    Even in Haskell, you would probably just write `f x = f1 x * f2 x`, unless you are a fan of the `Applictive` instance for functions, in which case you'd write `f = (*) <$> f1 <*> f2`. – chepner Dec 04 '18 at 20:49
  • @chickenNinja123: A slight advantage of `def` over the equivalent `lambda` is that it gives a more useful value for `repr(f)` (`` versus ` at 0x[...]>`). – dan04 Dec 04 '18 at 23:52
  • 1
    The *other* thing that Python is missing is operators that work on functions. Yes, you can define higher-order functions (functions that take functions as arguments and/or return functions), but not having operators makes writing such code cumbersome. `f ∘ g` would be both easier to read and (keyboard issues aside) write than `compose(f, g)`. `juxt(f,g)` might instead be written as `f ⊗ g` (to pick a suggestive, not necessarily ideal, operator from Unicode). (Naming is hard, but you can get used to a "good-enough" name.)... – chepner Dec 05 '18 at 13:49
  • ... (In Haskell, you can write `juxt(f,g)` as `f &&& g`. It might not be immediately obvious what `&&&` means, but it's not a bad mnemonic: `f &&& g` is a function that takes an argument and applies `f` **and** `g` to the same argument, returning the pair of results. – chepner Dec 05 '18 at 13:50

2 Answers2

5

TL;DR Such composition is a primitive (in the sense that it cannot be decomposed into other higher-order functions) operation that neither Python nor the tools module supports. You need to implement it yourself.


What you are missing (or rather, what Python and the tools module are missing) is the notion of an applicative functor. To understand what that means, let's first review two functions in the tools module:

  1. compose lets you chain together two functions. That is,

    compose(f,g) == lamba x: f(g(x))
    
  2. curry is related to partial application: a demonstration will be quicker than an explanation:

    curry(f)(x)(y) == f(x, y)
    

    That is, curry(f)(x) is basically the same as partial(f, x); both take a value y to return the value f(x, y).

In addition, a functor is basically a way to map a function over some value. You are no doubt familiar with the list functor:

map(f, [a,b,c]) == [f(a), f(b), f(c)]

Functions are also functors, but instead of map, we use compose. That is, mapping f over g produces compose(f, g).

Now, to combine mul, f1, and f2 into g = lambda x: g(f1(x), f2(x)), it seems like both compose and curry would be useful. To wit,

lambda x: mul(f1(x), f2(x)) == lambda x: curry(mul)(f1(x))(f2(x))

and

lambda x: mul(f1(x), f2(x)) == lambda x: compose(curry(mul), f1)(x)(f2(x))

(That is, curry is what allows us to compose a two-argument function with another function.)

But composition is, in some sense, strictly a linear operation; the input of one function comes from the output of another. The composition of mul and f1 creates a function that expects an argument and returns a function that expects that same argument. How do we move x out of the "middle" of either expression? What we need is some mystery function foo such that

foo(f, g) = lambda x: f(x, g(x))

that makes a function that passes its argument to both f and g, while also passing the result g(x) to f. With such a function foo, we could write

lambda x: foo(compose(curry(mul), f1), f2)

and get our desired result.

And that brings us to the idea of an applicative functor. It provides the necessary function foo

def foo(f, g):
   def _(x):
       return f(x, g(x))

that combines the notions of composition and currying that we don't currently have.

In other words, foo is a distinct primitive operation; you can't implement it in terms of composition itself.

Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268
chepner
  • 497,756
  • 71
  • 530
  • 681
1

If you really want to use an operator for such higher-order functions, you can make a decorator for it.

class AddableFunction:
    '''
    Function decorator that lets (f+g)(x) = f(x) + g(x).
    '''
    def __init__(self, function):
        self._function = function
    def __call__(self, *args, **kwargs):
        return self._function(*args, **kwargs)
    def __add__(self, other):
        return AddableFunction(lambda *args, **kwargs: self(*args, **kwargs) + other(*args, **kwargs))

@AddableFunction
def f(x):
    return x ** 2

@AddableFunction
def g(x):
    return x ** 3

print((f + g)(1)) # 2
print((f + g)(2)) # 12
print((f + g)(3)) # 36
dan04
  • 87,747
  • 23
  • 163
  • 198