Consider the following Python snippet concerning functions composition:
from functools import reduce
def compose(*funcs):
# compose a group of functions into a single composite (f(g(h(..(x)..)))
return reduce(lambda f, g: lambda *args, **kwargs: f(g(*args, **kwargs)), funcs)
### --- usage example:
from math import sin, cos, sqrt
mycompositefunc = compose(sin,cos,sqrt)
mycompositefunc(2)
I have two questions:
- Can someone please explain me the
compose
"operational logic"? (How it works?) - Would it be possible (and how?) to obtain the same thing without using reduce for this?
I already looked here, here and here too, my problem is NOT understanding what lambda
means or reduce
does (I think I got, for instance, that 2
in the usage example will be somewhat the first element in funcs
to be composed).
What I find harder to understand is rather the complexity of how the two lambda
s got combined/nested and mixed with *args, **kwargs
here as reduce
first argument ...
EDIT:
First of all, @Martijn and @Borealid, thank you for your effort and answers and for the time you are dedicating to me. (Sorry for the delay, I do this in my spare time and not always have a a lot...)
Ok, coming to facts now...
About 1st point on my question:
Before anything, I realized what I didn't really got (but I hope I did now) about those *args, **kwargs
variadic arguments before is that at least **kwargs
is not mandatory (I say well, right?)
This made me understand, for instance, why mycompositefunc(2)
works with that only one (non keyword) passed argument.
I realized, then, that the example would work even replacing those *args, **args
in the inner lambda with a simple x
. I imagine that's because, in the example, all 3 composed functions (sin, cos, sqrt
) expect one (and one only) parameter... and, of course, return a single result... so, more specifically, it works because the first composed function expect just one parameter (the following others will naturally get only one argument here, that's the result of the previous composed functions, so you COULDN'T compose functions that expect more than one argument after the first one... I know it's a bit contort but I think you got what I'm trying to explain...)
Now coming to what remains the real unclear matter for me here:
lambda f, g: lambda *args, **kwargs: f(g(*args, **kwargs))
How does that lambda nesting "magic" works?
With all the great respect you deserve and I bear you,
it seems to me like both of you are wrong coming to the conclusion the final result shall be: sqrt(sin(cos(*args, **kw)))
.
It actually can't be, the order of appliance of the sqrt function is clearly reversed: it's not the last to be composed but the first.
I say this because:
>>> mycompositefunc(2)
0.1553124117201235
its result is equal to
>>> sin(cos(sqrt(2)))
0.1553124117201235
whereas you get an error with
>>> sqrt(sin(cos(2)))
[...]
ValueError: math domain error
(that's due to trying to squareroot a negative float)
#P.S. for completeness:
>>> sqrt(cos(sin(2)))
0.7837731062727799
>>> cos(sin(sqrt(2)))
0.5505562169613818
So, I understand that the functions composition will be made from the last one to the first ( i.e. : compose(sin,cos,sqrt) => sin(cos(sqrt(x))) ) but the "why?" and how does that lambda nesting "magic" works? still remains a bit unclear for me... Help/Suggestions very appreciated!
On 2nd point (about rewriting compose without reduce)
@Martijn Pieters: your first compose (the "wrapped" one) works and returns exactly the same result
>>> mp_compfunc = mp_compose(sin,cos,sqrt)
>>> mp_compfunc(2)
0.1553124117201235
The unwrapped version, instead, unfortunately loops until RuntimeError: maximum recursion depth exceeded
...
@Borealid: your foo/bar example will not get more than two functions for composition but I think it was just for explanations not intended for answering to second point, right?