functools.compose to chain functions together
I'd like to express additional interest in python patch 1660179, discussed here: http://mail.python.org/pipermail/patches/2007-February/021687.html On several occasions, I've had the desire for something like this. I've made due with lambda functions, but as was mentioned, the lambda is clumsy and harder to read than functools.compose would be. A potentially common use-case is when a library has a multi-decorator use case in which they want to compose a meta decorator out of one or more individual decorators. Consider the hypothetical library. # we have three decorators we use commonly def dec_register_function_for_x(func): # do something with func return func def dec_alter_docstring(func): # do something to func.__doc__ return func def inject_some_data(data): def dec_inject_data(func): func.data = data # this may not be legal, but assume it does something useful return func return dec_inject_data # we could use these decorators explicitly throughout our project @dec_register_function_for_x @dec_alter_docstring @dec_inject_some_data('foo data 1') def our_func_1(params): pass @dec_register_function_for_x @dec_alter_docstring @dec_inject_some_data('foo data 2') def our_func_2(params): pass For two functions, that's not too onerous, but if it's used throughout the application, it would be nice to abstract the collection of decorators. One could do this with lambdas. def meta_decorator(data): return lambda func: dec_register_function_for_x(dec_alter_docstring(dec_inject_some_data(data)(f unc))) But to me, a compose function is much easier to read and much more consistent with the decorator usage syntax itself. def meta_decorator(data): return compose(dec_register_function_for_x, dec_alter_docstring, dec_inject_some_data(data)) The latter implementation seems much more readable and elegant. One doesn't even need to know the decorator signature to effectively compose meta_decorators. I've heard it said that Python is not a functional language, but if that were really the case, then functools would not exist. In addition to the example described above, I've had multiple occasions where having a general purpose function composition function would have simplified the implementation by providing a basic functional construct. While Python isn't primarily a functional language, it does have some functional constructs, and this is one of the features that makes Python so versatile; one can program functionally, procedurally, or in an object-oriented way, all within the same language. I admit, I may be a bit biased; my first formal programming course was taught in Scheme. Nevertheless, I believe functools is the ideal location for a very basic and general capability such as composition. I realize this patch was rejected, but I'd like to propose reviving the patch and incorporating it into functools. Regards, Jason
On 14 Aug 2009, at 20:39 , Jason R. Coombs wrote:
I've heard it said that Python is not a functional language, but if that were really the case, then functools would not exist. In addition to the example described above, I've had multiple occasions where having a general purpose function composition function would have simplified the implementation by providing a basic functional construct.
It's not like a basic variable-arity composition function is hard to implement though, it's basically: def compose(*funcs): return reduce(lambda f1, f2: lambda v: f1(f2(v)), funcs) it'll turn compose(a, b, c, d)(value) into a(b(c(d(value))))
It would be best to discuss this on comp.lang.python or python-ideas to get general support for the idea before trying to bring this to python-dev in hopes of changing people's minds. On Fri, Aug 14, 2009 at 11:39, Jason R. Coombs <jaraco@jaraco.com> wrote:
I’d like to express additional interest in python patch 1660179, discussed here:
http://mail.python.org/pipermail/patches/2007-February/021687.html
On several occasions, I’ve had the desire for something like this. I’ve made due with lambda functions, but as was mentioned, the lambda is clumsy and harder to read than functools.compose would be.
A potentially common use-case is when a library has a multi-decorator use case in which they want to compose a meta decorator out of one or more individual decorators.
Consider the hypothetical library.
# we have three decorators we use commonly
def dec_register_function_for_x(func):
# do something with func
return func
def dec_alter_docstring(func):
# do something to func.__doc__
return func
def inject_some_data(data):
def dec_inject_data(func):
func.data = data # this may not be legal, but assume it does something useful
return func
return dec_inject_data
# we could use these decorators explicitly throughout our project
@dec_register_function_for_x
@dec_alter_docstring
@dec_inject_some_data(‘foo data 1’)
def our_func_1(params):
pass
@dec_register_function_for_x
@dec_alter_docstring
@dec_inject_some_data(‘foo data 2’)
def our_func_2(params):
pass
For two functions, that’s not too onerous, but if it’s used throughout the application, it would be nice to abstract the collection of decorators. One could do this with lambdas.
def meta_decorator(data):
return lambda func: dec_register_function_for_x(dec_alter_docstring(dec_inject_some_data(data)(func)))
But to me, a compose function is much easier to read and much more consistent with the decorator usage syntax itself.
def meta_decorator(data):
return compose(dec_register_function_for_x, dec_alter_docstring, dec_inject_some_data(data))
The latter implementation seems much more readable and elegant. One doesn’t even need to know the decorator signature to effectively compose meta_decorators.
I’ve heard it said that Python is not a functional language, but if that were really the case, then functools would not exist. In addition to the example described above, I’ve had multiple occasions where having a general purpose function composition function would have simplified the implementation by providing a basic functional construct. While Python isn’t primarily a functional language, it does have some functional constructs, and this is one of the features that makes Python so versatile; one can program functionally, procedurally, or in an object-oriented way, all within the same language.
I admit, I may be a bit biased; my first formal programming course was taught in Scheme.
Nevertheless, I believe functools is the ideal location for a very basic and general capability such as composition.
I realize this patch was rejected, but I’d like to propose reviving the patch and incorporating it into functools.
Regards,
Jason
_______________________________________________ Python-Dev mailing list Python-Dev@python.org http://mail.python.org/mailman/listinfo/python-dev Unsubscribe: http://mail.python.org/mailman/options/python-dev/brett%40python.org
On Sat, 15 Aug 2009 04:39:03 am Jason R. Coombs wrote:
I'd like to express additional interest in python patch 1660179, discussed here:
http://mail.python.org/pipermail/patches/2007-February/021687.html [...] But to me, a compose function is much easier to read and much more consistent with the decorator usage syntax itself.
def meta_decorator(data): return compose(dec_register_function_for_x, dec_alter_docstring, dec_inject_some_data(data))
Surely that's better written as: meta_decorator = compose(dec_register_function_for_x, dec_alter_docstring, dec_inject_some_data)
I admit, I may be a bit biased; my first formal programming course was taught in Scheme.
Mine wasn't -- I've never even used Scheme, or Lisp, or any other functional language. But I've come to appreciate Python's functional tools, and would like to give a +0.5 to compose(). +1 if anyone can come up with additional use-cases. -- Steven D'Aprano
Steven D'Aprano wrote:
Sent: Sunday, 16 August, 2009 08:15
On Sat, 15 Aug 2009 04:39:03 am Jason R. Coombs wrote:
def meta_decorator(data): return compose(dec_register_function_for_x, dec_alter_docstring, dec_inject_some_data(data))
Surely that's better written as:
meta_decorator = compose(dec_register_function_for_x, dec_alter_docstring, dec_inject_some_data)
I agree. The former looks unnecessarily complicated. I purposely chose a non-trivial use case, one which involves a decorator that requires a parameter and thus must be called first before the actual decorator is returned. I think for this reason, the former syntax must be used so that the meta_decorator also takes the data parameter and constructs the proper "inject" decorator. Put another way, both dec_inject_some_data and meta_decorator are more like decorator factories. I suspect a simpler, and more common use-case would be like the one you described, where either data is global or the "inject" decorator is not used: meta_decorator = compose(dec_register_function_for_x, dec_alter_docstring)
Mine wasn't -- I've never even used Scheme, or Lisp, or any other functional language. But I've come to appreciate Python's functional tools, and would like to give a +0.5 to compose(). +1 if anyone can come up with additional use-cases.
Thanks for the interest. I decided to search through some of my active code for lambdas and see if there are areas where I would prefer to be using a compose function instead of an explicit lambda/reduce combination. I only found one such application; I attribute this limited finding to the fact that I probably elected for a procedural implementation when the functional implementation might have proven difficult to read, esp. with lambda. 1) Multiple string substitutions. You have a list of functions that operate on a string, but you want to collect them into a single operator that can be applied to a list of strings. sub_year = lambda s: s.replace("%Y", "2009") fix_strings_with_substituted_year = compose(str.strip, textwrap.dedent, sub_year) map(fix_strings_with_substituted_year, target_strings) Moreover, it would be great to be able to accept any number of substitutions. substitutions = [sub_year, sub_month, ...] fix_strings_with_substitutions = compose(str.strip, textwrap.dedent, *substitutions) I did conceive of another possibly interesting use case: vector translation. Consider an application that performs mathematical translations on n-dimensional vectors. While it would be optimal to use optimized matrix operations to perform these translations, for the sake of this example, all we have are basic Python programming constructs. At run-time, the user can compose an experiment to be conducted on his series of vectors. To do this, he selects from a list of provided translations and can provide his own. These translations can be tagged as named translations and thereafter used as translations themselves. The code might look something like: translations = selected_translations + custom_translations meta_translation = compose(*translations) save_translation(meta_translation, "My New Translation") def run_experiment(translation, vectors): result = map(translation, vectors) # do something with result Then, run_experiment can take a single translation or a meta-translation such as the one created above. This use-case highlights that a composed functions must take and return exactly one value, but that the value need not be a primitive scalar. I'm certain there are other, more obscure examples, but I feel these two use cases demonstrate some fairly common potential use cases for something like a composition function. Jason
Jason R. Coombs <jaraco <at> jaraco.com> writes:
I'm certain there are other, more obscure examples, but I feel these two use
cases demonstrate some fairly
common potential use cases for something like a composition function.
I also think it would be a nice addition. (but someone has to propose a patch :-)) Regards Antoine.
[Antoine Pitrou]
I also think it would be a nice addition. (but someone has to propose a patch :-))
I agree with Martin's reasons for rejecting the feature request (see the bug report for his full explanation). IIRC, the compose() idea had come-up and been rejected in previous discussions as well. At best, it will be a little syntactic sugar (though somewhat odd because the traditional mathematical ordering of a composition operator is the opposite of what intuition would suggest). At worst, it will be slower and less flexible than our normal ways of linking functions together. IMO, its only virtue is that people coming from functional languages are used to having compose. Otherwise, it's a YAGNI. Raymond
Raymond Hettinger wrote: Sent: Sunday, 16 August, 2009 12:42
[Antoine Pitrou]
I also think it would be a nice addition. (but someone has to propose a patch :-))
The patch was proposed and rejected here: http://bugs.python.org/issue1660179; my reason for mentioning it here is because the functionality isn't YAGNI for me; It seems like a fundamental capability when employing a functional programming paradigm.
I agree with Martin's reasons for rejecting the feature request (see the bug report for his full explanation). IIRC, the compose() idea had come-up and been rejected in previous discussions as well.
At best, it will be a little syntactic sugar (though somewhat odd because the traditional mathematical ordering of a composition operator is the opposite of what intuition would suggest). At worst, it will be slower and less flexible than our normal ways of linking functions together.
IMO, its only virtue is that people coming from functional languages are used to having compose. Otherwise, it's a YAGNI.
Right. I have great respect for your and Martin's original conclusion. The reason I came across the old patch was because I was searching for something that did exactly what compose does. That is to say, I had a use case that was compelling enough that I thought there should be something in functools to do what I wanted. I've encountered this pattern often enough that it might be in the stdlib. As it turns out, it isn't. For this reason, I wanted to voice my opinion that contradicts the conclusion of the previous patch discussion. Specifically, YAGNI doesn't apply to my experiences, and it does seem to have broad, fundamental application, especially with respect to functional programming. I'm not arguing that just because Jason needs it, it should be in the standard library. Rather, I just wanted to express that, like Chris AtLee, I would find this function quite useful. As Steven pointed out, this functionality is desirable even for those without a functional programming background. I'd like to mention also that even though I learned to program in Scheme in 1994, I haven't used it since, and I've been using Python since 1996, so my affinity for this function is based almost entirely from experiences programming in Python and not in a primarily functional language. If the Python community still concurs that 'compose' is YAGNI or otherwise undesirable, I understand. I just wanted to share my experiences and motivations as they pertain to the discussion. If it turns out that it's included in the stdlib later, all the better. Respectfully, Jason
The reason I came across the old patch was because I was searching for something that did exactly what compose does. That is to say, I had a use case that was compelling enough that I thought there should be something in functools to do what I wanted. I've encountered this pattern often enough that it might be in the stdlib.
Can you kindly give one or two examples of where compose would have been useful? Regards, Martin
Martin v. Löwis wrote:
The reason I came across the old patch was because I was searching for something that did exactly what compose does. That is to say, I had a use case that was compelling enough that I thought there should be something in functools to do what I wanted. I've encountered this pattern often enough that it might be in the stdlib.
Can you kindly give one or two examples of where compose would have been useful?
I went back in the archives and found your example. What I now don't understand is why you say that a compose function would be easier to read than a lambda expression. Can you please elaborate on that? I deeply believe that it is *harder* to read than a lambda expression, because the lambda expression makes the evaluation order clear, whereas the compose function doesn't (of course, function decorators ought to be commutative, so in this case, lack of clear evaluation order might be less important). Regards, Martin
Jason R. Coombs wrote:
I had a use case that was compelling enough that I thought there should be something in functools to do what I wanted.
I think this is one of those things that a small minority of people would use frequently, but everyone else would use very rarely or never. The decision on whether to include something in the stdlib needs to be based on the wider picture. In this case, it's trivial to write your own if you want it. As they say, "not every one-line function needs to be in the stdlib". -- Greg
Greg Ewing wrote:
Jason R. Coombs wrote:
I had a use case that was compelling enough that I thought there should be something in functools to do what I wanted.
I think this is one of those things that a small minority of people would use frequently, but everyone else would use very rarely or never. The decision on whether to include something in the stdlib needs to be based on the wider picture.
In this case, it's trivial to write your own if you want it. As they say, "not every one-line function needs to be in the stdlib".
I have never found these arguments compelling. They are obviously not true (e.g., itertools.compress()[1] added in 2.7/3.1), and so what I really hear is: "I don't like it and I outrank you." I can't help invoke part of PEP309's justification for functools.partial()[2]: """ I agree that lambda is usually good enough, just not always. And I want the possibility of useful introspection and subclassing. """ The same reasoning would seem to apply here. In the OP's example, the meta-decorator becomes opaque due to the use of a lambda. If one could introspect a compose(), then introspection tools could actually know the set of decorators being applied. As it is, the "preferred" method of using a lambda actually makes it quite hard to know anything. class compose(): def __init__(self, *funcs): if not funcs: raise ValueError(funcs) self.funcs = funcs def __call__(self, *args, **kwargs): v = self.funcs[-1](*args, **kwargs) for func in reversed(self.funcs[:-1]): v = func(v) return v meta = functools.compose(decorator_a, decorator_b) print meta.funcs meta = lambda f: decorator_a(decorator_b(f)) # impossible, short of disassembling the lambda -Scott [1] http://docs.python.org/3.1/library/itertools.html#itertools.compress """ def compress(data, selectors): # compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F return (d for d, s in zip(data, selectors) if s) """
I have never found these arguments compelling. They are obviously not true (e.g., itertools.compress()[1] added in 2.7/3.1), and so what I really hear is: "I don't like it and I outrank you."
That certainly contributes to it - if you are not a committer, you have to find a committer that finds the feature important enough to work with you to integrate it. Fortunately, there is a process to overcome this problem: the PEP process. If you you really really want the feature, and can't find a committer that supports it yet, write a PEP. Then it will be up to Guido van Rossum to reject it.
The same reasoning would seem to apply here. In the OP's example, the meta-decorator becomes opaque due to the use of a lambda. If one could introspect a compose(), then introspection tools could actually know the set of decorators being applied. As it is, the "preferred" method of using a lambda actually makes it quite hard to know anything.
That makes it even more necessary to write a PEP. I would have never guessed that introspection on the compose result is desirable. AFAICT, operator.attrgetter isn't introspectable, either, nor would the patch proposed in #7762 give you an introspectable getter. ISTM that people have fairly different requirements wrt. that feature. Regards, Martin
Raymond Hettinger <python <at> rcn.com> writes:
IMO, its only virtue is that people coming from functional languages are used to having compose. Otherwise, it's a YAGNI.
Then I wonder how partial() ended up in the stdlib. It seems hardly more useful than compose(). Either we decide it is useful to have a set of basic "functional" tools in the stdlib, and both partial() and compose() have their place there, or we decide functools has no place in the stdlib at all. Providing a half-assed module is probably frustrating to its potential users. (not being particularly attached to functional tools, I still think compose() has its value, and Jason did a good job of presenting potential use cases) Regards Antoine.
Then I wonder how partial() ended up in the stdlib.
PEP 309 was written, discussed, approved, and implemented - that's how partial ended up in the stdlib. The feature itself might be debatable, that's what we have the PEP process for.
Either we decide it is useful to have a set of basic "functional" tools in the stdlib, and both partial() and compose() have their place there, or we decide functools has no place in the stdlib at all. Providing a half-assed module is probably frustrating to its potential users.
So write a PEP and propose to enhance the standard library.
(not being particularly attached to functional tools, I still think compose() has its value, and Jason did a good job of presenting potential use cases)
I don't think he did. Comparing it to the one obvious solution (use a lambda expression), his only reasoning was "it is much easier to read". I truly cannot believe that a compose function would be easier to read to the average Python programmer: if you have def foo(data): return compose(a, b(data), c) what would you expect that to mean? Please rewrite it as a regular Python expression, preferably without looking at the patch that has been proposed first. I bet there is a 50% chance that you get it wrong (because there are two possible interpretations). Regards, Martin
PEP 309 was written, discussed, approved, and implemented - that's how partial ended up in the stdlib.
Ok, I'm surprised that a single addition to a module needed a PEP in order to be approved. Interestingly, here's what the summary section in PEP 309 says: « A standard library module functional should contain an implementation of partial, /and any other higher-order functions the community want/. » (emphasis mine)
I truly cannot believe that a compose function would be easier to read to the average Python programmer: if you have
def foo(data): return compose(a, b(data), c)
what would you expect that to mean? Please rewrite it as a regular Python expression, preferably without looking at the patch that has been proposed first.
Ok, here's my attempt without looking at the patch: def foo(data): def bar(*args, **kwargs): return a(b(data)(c(*args, **kwargs))) return bar Whether or not it is easier to read to the "average Python programmer" is not that important I think. We have lots of things that certainly aren't, and yet still exist (all of the functions in the operator module, for example; or `partial` itself for that matter). They are there for advanced programmers. Regards Antoine.
PEP 309 was written, discussed, approved, and implemented - that's how partial ended up in the stdlib.
Ok, I'm surprised that a single addition to a module needed a PEP in order to be approved.
A PEP is generally needed if there is no easy consent achievable. It's not (primarily) the size of a feature that determines the need for a formal process, but but whether the community considers a certain change "obviously" correct and desirable.
def foo(data): return compose(a, b(data), c)
Ok, here's my attempt without looking at the patch:
def foo(data): def bar(*args, **kwargs): return a(b(data)(c(*args, **kwargs))) return bar
Ok, that's also what the patch has proposed. I was puzzled when I read l.sort(key=compose(itemgetter(1), itemgetter(0)))) because I expected it to mean l.sort(key=lambda x:x[1][0]) when it would really mean l.sort(key=lambda x:x[0][1])
Whether or not it is easier to read to the "average Python programmer" is not that important I think.
I completely disagree. It is one of Python's strength that it is "executable pseudo-code", which originates from the code being easy to read, and meaning the obvious thing even to a reader not familiar with the language. The proposed compose function breaks this important property, in a way that allows misinterpretation (i.e. you think you know what it does, and it actually does something different). I, personally, was not able to understand the compose function correctly, so I remain opposed.
We have lots of things that certainly aren't, and yet still exist (all of the functions in the operator module, for example; or `partial` itself for that matter). They are there for advanced programmers.
It's quite ok if only advanced programmers know that they are there, and know how to write them. However, I still think it is desirable that "lesser" programmers are then able to read them, or atleast notice that they mean something that they will need to learn first (such as a keyword they had never seen before). Regards, Martin
Le lundi 17 août 2009 à 09:07 +0200, "Martin v. Löwis" a écrit :
Ok, that's also what the patch has proposed. I was puzzled when I read
l.sort(key=compose(itemgetter(1), itemgetter(0))))
because I expected it to mean
l.sort(key=lambda x:x[1][0])
But that's itemgetter's fault, not compose's. Because itemgetter's obvious equivalent (the [] operator) uses postfix notation, combining several itemgetters reverses the lexical order of appearance. Besides, the argument order is similar to the one in the function composition notation in mathematics (which isn't really advanced stuff and should have been taught to every former scientific/technical student out there). Regards Antoine.
Antoine Pitrou wrote:
PEP 309 was written, discussed, approved, and implemented - that's how partial ended up in the stdlib.
Ok, I'm surprised that a single addition to a module needed a PEP in order to be approved.
It makes a little more sense once you realise that there was no functools module before the implementation of PEP 309. The other functions it contains in Python 2.5 (update_wrapper() and wraps()) were added later in the development cycle and reduce() didn't get added to it until 2.6/3.0. If a concrete proposal is made that emphasises the improved introspection capabilities and raw speed increase that a function composition approach can offer over the use of lambda then I'd personally be willing to support this idea, since it was at least in part those two ideas that sold the idea of partial(). (partial() did have a big advantage over compose() in that the former's readability gains were far more obvious to most readers). Cheers, Nick. P.S. PEP 309 is wrong when it says a C version probably isn't worthwhile - between the time the PEP was first implemented and the time 2.5 was actually released, enough investigation was done to show that the speed gain from Hye-Shik Chang's C version was well worth the additional code complexity. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia ---------------------------------------------------------------
Le lundi 17 août 2009 à 20:53 +1000, Nick Coghlan a écrit :
P.S. PEP 309 is wrong when it says a C version probably isn't worthwhile - between the time the PEP was first implemented and the time 2.5 was actually released, enough investigation was done to show that the speed gain from Hye-Shik Chang's C version was well worth the additional code complexity.
Yes, one-line Python wrappers can kill performance of pure C code. Seen that in the IO lib, again. Regards Antoine.
On Mon, 17 Aug 2009 08:10:16 am Martin v. Löwis wrote:
I don't think he did. Comparing it to the one obvious solution (use a lambda expression), his only reasoning was "it is much easier to read". I truly cannot believe that a compose function would be easier to read to the average Python programmer: if you have
def foo(data): return compose(a, b(data), c)
what would you expect that to mean?
foo is a factory function that, given an argument `data`, generates a function b(data), then composes it with two other functions a and c, and returns the result, also a function.
Please rewrite it as a regular Python expression, preferably without looking at the patch that has been proposed first. I bet there is a 50% chance that you get it wrong (because there are two possible interpretations).
But surely only one of them will agree with the standard definition of function composition. Both Mathworld and Wikipedia agree that f∘g(x) is equivalent to f(g(x)): http://mathworld.wolfram.com/Composition.html http://en.wikipedia.org/wiki/Function_composition and I don't see any reason why a compose() function shouldn't do the same. (Aside: how do I look at the patch? The only link I have is here: http://mail.python.org/pipermail/patches/2007-February/021687.html but I can't see how to get to the patch from there.) foo could be written as: def foo(data): return lambda *args, **kwargs: a(b(data)(c(*args, **kwargs))) Or without lambda: def foo(data): def composed(*args, **kwargs): return a(b(data)(c(*args, **kwargs))) return composed This soon gets unwieldy: def foo(arg1, arg2, arg3): return compose( f, g, h, factory(arg1), factory(arg2), factory(arg3) ) versus def foo(arg1, arg2, arg3): return lambda *a, **kw: ( f(g(h(factory(arg1)(factory(arg2)(factory(arg3)(*a, **kw)))))) ) but presumably composing six functions is rare. A further advantage of compose() is that one could, if desired, generate a sensible name and doc string for the returned function. Depends on how heavyweight you want compose() to become. I think the compose() version is far more readable and understandable, but another factor is the performance cost of the generated function compared to a hand-made lambda. For the record, Haskell makes compose a built-in operator: http://www.haskell.org/haskellwiki/Function_composition It doesn't appear to be standard in Ruby, but it seems to be commonly requested, and a version is on Facets: http://facets.rubyforge.org/apidoc/api/core/classes/Proc.html#M000161 -- Steven D'Aprano
On 17 Aug 2009, at 09:43 , Steven D'Aprano wrote:
On Mon, 17 Aug 2009 08:10:16 am Martin v. Löwis wrote:
I don't think he did. Comparing it to the one obvious solution (use a lambda expression), his only reasoning was "it is much easier to read". I truly cannot believe that a compose function would be easier to read to the average Python programmer: if you have
def foo(data): return compose(a, b(data), c)
what would you expect that to mean?
foo is a factory function that, given an argument `data`, generates a function b(data), then composes it with two other functions a and c, and returns the result, also a function.
From his messages, I think Martin's issue with `compose` is with the composition order rather than the fact that it "pipes" functions: compose uses the mathematical order, (f ∘ g)(x) = f(g(x)) (so g, the last function of the composition, is applied first), rather than a "shell pipe" order of `(f >>> g)(x) = g(f(x))` (where g, the last function of the composition, is applied last).
For the record, Haskell makes compose a built-in operator:
Yes, but Haskell also has a left-to-right composition, the (>>>) operator: http://haskell.org/ghc/docs/latest/html/libraries/base/Control-Arrow.html#v :>>>
and I don't see any reason why a compose() function shouldn't do the same.
I was tricked into reading it different when used with getters, i.e. l.sort(key=compose(attrgetter('name'),attrgetter('age'))) is too easy (IMO) to read as applying foo.name.age on all elements of the list.
(Aside: how do I look at the patch? The only link I have is here: http://mail.python.org/pipermail/patches/2007-February/021687.html but I can't see how to get to the patch from there.)
It's best to search for "compose" in the bug tracker: http://bugs.python.org/issue1660179 Regards, Martin
Antoine Pitrou wrote:
Raymond Hettinger <python <at> rcn.com> writes:
IMO, its only virtue is that people coming from functional languages are used to having compose. Otherwise, it's a YAGNI.
Then I wonder how partial() ended up in the stdlib. It seems hardly more useful than compose().
I would certainly consider it more useful, but that aside, it's also a lot simpler to understand and use than the proposed compose() function. I think the main difference is that compose() requires functional/math skills to be used and read correctly (and might still be surprising in some corner cases), whereas partial() only requires you to understand how to set a function argument. Totally different level of mental complexity, IMHO. Stefan
On Mon, 17 Aug 2009 07:14:05 pm Stefan Behnel wrote:
Antoine Pitrou wrote:
Raymond Hettinger <python <at> rcn.com> writes:
IMO, its only virtue is that people coming from functional languages are used to having compose. Otherwise, it's a YAGNI.
Then I wonder how partial() ended up in the stdlib. It seems hardly more useful than compose().
I would certainly consider it more useful, but that aside, it's also a lot simpler to understand and use than the proposed compose() function. I think the main difference is that compose() requires functional/math skills to be used and read correctly (and might still be surprising in some corner cases), whereas partial() only requires you to understand how to set a function argument. Totally different level of mental complexity, IMHO.
I find the opposite -- compose() seems completely simple and straight-forward to me, while partial() is still a mystery no matter how many times I use it. I always have to look it up to see which way it binds. Putting that aside, partial() too is easy enough to implement with lambda: partial(f, 2) is the same as lambda *args: f(2, *args). To my mind, there are two important reasons for preferring named functions like partial() and compose() over lambda solutions: * performance: a good C implementation should be better than a pure-Python lambda; and * specificity: there's only one thing compose() or partial() could do, whereas a lambda is so general it could do anything. Contrast: compose(f, g, h) lambda x: f(g(h(x))) You need to read virtually the entire lambda before you can distinguish it from some other arbitrary lambda: lambda x: f(g(h))(x) lambda x: f(g(x) or h(x)) lambda x: f(g(x)) + h(x) etc. -- Steven D'Aprano
participants (12)
-
"Martin v. Löwis"
-
Antoine Pitrou
-
Brett Cannon
-
Greg Ewing
-
Jason R. Coombs
-
Nick Coghlan
-
Raymond Hettinger
-
Scott Dial
-
Stefan Behnel
-
Steven D'Aprano
-
Xavier Morel
-
Xavier Morel