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:
compose
"operational logic"? (How it works?)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?
Function composition is the way of combining two or more functions in such a way that the output of one function becomes the input of the second function and so on.
In Python, a function is a group of related statements that performs a specific task. Functions help break our program into smaller and modular chunks. As our program grows larger and larger, functions make it more organized and manageable. Furthermore, it avoids repetition and makes the code reusable.
In Maths, the composition of a function is an operation where two functions say f and g generate a new function say h in such a way that h(x) = g(f(x)). It means here function g is applied to the function of x. So, basically, a function is applied to the result of another function.
The *args, **kw
syntax in both the lambda
signature and call syntax are the best way to pass on arbitrary arguments. They accept any number of positional and keyword arguments and just pass those on to a next call. You could write the result of the outer lambda
as:
def _anonymous_function_(*args, **kw):
result_of_g = g(*args, **kw)
return f(result_of_g)
return _anonymous_function
The compose
function can be rewritten without reduce()
like this:
def compose(*funcs):
wrap = lambda f, g: lambda *args, **kw: f(g(*args, **kw))
result = funcs[0]
for func in funcs[1:]:
result = wrap(result, func)
return result
This does the exact same thing as the reduce()
call; call the lambda for the chain of functions.
So, the first two functions in the sequence are sin
and cos
, and these are replaced by:
lambda *args, **kw: sin(cos(*args, **kw))
This then is passed to the next call as f
, with sqrt
g
, so you get:
lambda *args, **kw: (lambda *args, **kw: sin(cos(*args, **kw)))(sqrt(*args, **kw)))
which can be simplified to:
lambda *args, **kw: sin(cos(sqrt(*args, **kw)))
because f()
is a lambda that passes its arguments to the nested sin(cos())
call.
In the end, then, you have produced a function that calls sqrt()
, the result of which is passed to cos()
, and the output of that is then passed to sin()
. The *args, **kw
lets you pass in any number of arguments or keyword arguments, so the compose()
function can be applied to anything than is callable, provided that all but the first function takes just one argument, of course.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With