On 10.5.2015 5:56, Steven D'Aprano wrote:
You could in addition have:
spam @ eggs @ cheese @ arg # equivalent to spam(eggs(cheese(arg)))
arg | spam | eggs | cheese # equivalent to cheese(eggs(spam(arg)))
Here, arg would thus be recognized as not a function. No. I think it is absolutely vital to distinguish by syntax the difference between composition and function application, and not try to "do what I mean". DWIM software has a bad history of doing the wrong thing.
Every other kind of callable uses obj(arg) to call it: types, functions, methods, partial objects, etc. We shouldn't make function composition try to be different. If I write sqrt@100 I should get a runtime error, not 10.
I don't mind if the error is delayed until I actually try to call the composed object, but at some point I should get a TypeError that 100 is not callable.
That is in fact a part of why I added a function call () to the sketch in my recent post (extended partial operator, there using ->). This way, the composition operator would never do the actual call by itself, but instead make a partial. But I admit that (sqrt@100)() still would give 10, not the runtime error you want (which may indeed cause problems with callable arguments). It only solves half the problem.
Another way to feed the left-to-right | composition from the left would of course be
(feed(x) | spam | eggs | cheese)() # feed would be just def feed(x): return x
But I'm not sure I like it. Luckily, (cheese @ eggs @ spam)(x) does not have this problem. However, if cheese, eggs and spam were matrix transformations, one would write
cheese @ eggs @ spam @ x
But perhaps numpy would want to bridge this gap with extended behavior (allow calling numpy functions with @ or "calling a matrix transformation" with () ). Or perhaps not :).