A more readable way to nest functions
HI Everyone, I’m relatively new to the world of python but in my short time here I’ve fallen in love with how readable this language is. One issue that I’ve seen in a lot of languages struggle with is nested function calls. Parenthesis when nested inherently create readability issues. I stumbled upon what I believe is an elegant solution within the elm platform in their use of the backward pipe operator <|. Current Ex. Suggested Structure This aligns with the Zen of Python in the following ways Simple is better than complex Flat is better than nested Sparse is better than dense Readability counts Practicality beats purity Ways it may conflict Explicit is better than implicit Special cases aren't special enough to break the rules Just curious to see what the rest of the community thinks 😊 Best Regards, Brent
Hi,
This would break apart as soon as one of left functions takes more than one
parameter.
Best regards,
João Santos
On Fri, 27 Jan 2017, 22:08 Brent Brinkley,
HI Everyone,
I’m relatively new to the world of python but in my short time here I’ve fallen in love with how readable this language is. One issue that I’ve seen in a lot of languages struggle with is nested function calls. Parenthesis when nested inherently create readability issues. I stumbled upon what I believe is an elegant solution within the elm platform in their use of the backward pipe operator <|.
Current Ex.
Suggested Structure
This aligns with the Zen of Python in the following ways
- Simple is better than complex - Flat is better than nested - Sparse is better than dense - Readability counts - Practicality beats purity
Ways it may conflict
- Explicit is better than implicit - Special cases aren't special enough to break the rules
Just curious to see what the rest of the community thinks 😊
Best Regards,
Brent _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On 01/27/2017 01:07 PM, Brent Brinkley wrote:
I’m relatively new to the world of python
Welcome!
but in my short time here I’ve fallen in love with how readable this language is. One issue that I’ve seen in a lot of languages struggle with is nested function calls. Parenthesis when nested inherently create readability issues. I stumbled upon what I believe is an elegant solution within the elm platform in their use of the backward pipe operator <|.
Please use text -- it save responders from having to reenter the non-text content>
Suggested structure:
print() <| some_func() <| another_func("Hello")
My first question is what does this look like when print() and some_func() have other parameters? In other words, what would this look like? print('hello', name, some_func('whatsit', another_func('good-bye')), sep=' .-. ') Currently, I would format that as: print( 'hello', name, some_func( 'whatsit', another_func( 'good-bye') ), ), sep=' .-. ', ) Okay, maybe a few more new-lines than such a short example requires, but that's the idea. -- ~Ethan~
On Fri, Jan 27, 2017 at 3:28 PM, Ethan Furman
On 01/27/2017 01:07 PM, Brent Brinkley wrote:
Suggested structure:
print() <| some_func() <| another_func("Hello")
My first question is what does this look like when print() and some_func() have other parameters? In other words, what would this look like?
print('hello', name, some_func('whatsit', another_func('good-bye')), sep=' .-. ')
The Elixir pipe operator looks pretty close to the suggested style, but the argument order is reversed: another_func('good-bye') |> some_func('whatsit') |> print('hello', name, sep=' .-. ') This isn't exactly equivalent to the example though because the result of each call is passed as the first argument to the next function. I think it looks nice when it's the right fit, but it's limited to the first argument. -- C Anthony
On Fri, 27 Jan 2017 at 21:29 Ethan Furman
Suggested structure:
print() <| some_func() <| another_func("Hello")
My first question is what does this look like when print() and some_func() have other parameters? In other words, what would this look like? print('hello', name, some_func('whatsit', another_func('good-bye')), sep=' .-. ') This idea doesn't solve the general problem well, but I'm not convinced that it needs to; that can be addressed by making partial function application syntax nicer. Although I think it's probably fairly useful anyway. FWIW, I'd spell it without the (), so it's simply a right-associative binary operator on expressions, (a -> b, a) -> b, rather than magic syntax. print XYZ some_func XYZ another_func("Hello")
On Sat, Jan 28, 2017 at 11:41 PM, Ed Kellett
FWIW, I'd spell it without the (), so it's simply a right-associative binary operator on expressions, (a -> b, a) -> b, rather than magic syntax.
print XYZ some_func XYZ another_func("Hello")
I'm not entirely sure I understand your example; are you using "XYZ" as an operator, or a token (another parameter)? I think probably the former, but you may mean this differently. In any case, it's a new syntax that does exactly the same thing that we can already do, just with more restrictions, and arguably more readably. That means it has to have a SIGNIFICANT advantage over the current syntax - a pretty high bar. I don't see that it's cleared that bar; it is, at best, a small and incremental change. ChrisA
Hi list o/ This idea sounds fun, so as a thought experiment why not imagine one way of integrating it in what I believe would be pythonic enough. On Sat, Jan 28, 2017 at 12:41:24PM +0000, Ed Kellett wrote:
FWIW, I'd spell it without the (), so it's simply a right-associative binary operator on expressions, (a -> b, a) -> b, rather than magic syntax. print XYZ some_func XYZ another_func("Hello")
I agree this would look a bit more elegant. To focus on the feature of that operator, instead of how to write it, I'll use XYZ instead of <| in this post. So, considering it's decided that the RHS is in charge of filling up all the arguments of the LHS, how to deal with positional and keyword arguments without introducing new syntax? Should it be by returning a tuple of positional iterable and keyword dict? i.e.: def fn_a(*args, **kwarg): print("args: {}, kwarg: {}".format(args, kwarg)) def fn_b(): return (1,2,3), {'a':1, 'b':2, 'c':3} fn_a XYZ fn_b() but then if we pass only positional would the following be ok? def fn_b(): return (1,2,3) or should it look like this one, it being a tuple, but with the second part being empty: def fn_b(): return (1,2,3), so to avoid confusing if we want to pass a dict as second positional argument of fn_a(): def fn_b(): return (1, {'a': 2}), anyway, I guess it's pretty safe to assume that if fn_b() returns a scalar, it'll be easy to assume it's just a single positional argument. That being said, then if the chosen syntax is like the following:
print XYZ some_func XYZ another_func("Hello")
and given we decide to apply the rules I'm suggesting above, why not make this function dumb simple, it being: * "take the RHS scalar or tuple, and apply it as arguments to the LHS" Meaning that the above could also be written as: print XYZ some_func XYZ another_func XYZ "Hello" Then the basic operator definition could be done with a dunder looking like: def __application__(self, other): if isinstance(other, Iterable): if (len(other) == 2 and isinstance(other[0], tuple) and isinstance(other[1], dict)): return self(*other[0], **other[1]) elif (len(other) == 1 and isinstance(other[0], tuple): return self(*other[0]) return self(other) In practice, such a scheme would make it possible to have: print XYZ (("Hello World",), {"file": sys.stderr}) Another thing I'm wondering, should the whole syntax be an expression? I believe it should, so it fits in python3 logic of everything — except control statements — is an expression: print(fn_a XYZ fn_b(), file=sys.stderr) But the danger is that it might lead to very long lines: print XYZ my_first_function XYZ my_second_function XYZ my_third_function XYZ my_fourth_function leading to either continuing spaces or wrapping in parenthesis: print XYZ my_first_function \ XYZ my_second_function \ XYZ my_third_function \ XYZ my_fourth_function (print XYZ my_first_function XYZ my_second_function XYZ my_third_function XYZ my_fourth_function) but it would then be avoiding the silly stack of closing parenthesis: print(my_first_function( my_second_function( my_third_function( my_fourth_function())))) All in all, it can be a nice syntactic sugar to have which could make it more flexible working with higher order functions, but it with the way I'm suggesting to comply with python's arguments handling, it offers little advantages when the RHS is not filling LHS arguments: >>> print(all(map(lambda x: x>2, filter(lambda x: isinstance(x, int), range(0,3))))) True vs >>> print XYZ all XYZ map XYZ (lambda x: x>2, filter(lambda x: isinstance(x, int), range(0,3))), True Here, applying map onto all onto print offers a great readability, but for passing arguments to map, not so much. So the question end up being: is application of *all* arguments of a function from return value of another function a common enough pattern to justify a new syntax that would make it better *only* then? Or maybe instead of passing a tuple of parameters could we stack parameters up with the XYZ operator up until a callable is reached, so that: >>> print XYZ all XYZ map XYZ lambda x: x>2 XYZ filter XYZ lambda x: isinstance(x, int) XYZ range(0,3) But then how can it be told that we want: `(lambda x: isinstance(x), range(0,3)` to be fed to `filter`, and not `range(0,3)` to be fed to `lambda x: isinstance(x, int)`? But then it would be just another way to introduce currying as a language feature with an operator, so we should then just discuss on how to add currying as a language syntax "by the book", but I'm pretty sure that's a topic already discussed before I joined this list ;-) that was my €0.02 Cheers, -- zmo
On Sat, 28 Jan 2017 at 14:27 zmo via Python-ideas
I agree this would look a bit more elegant. To focus on the feature of that operator, instead of how to write it, I'll use XYZ instead of <| in this post.
My thoughts exactly :)
So, considering it's decided that the RHS is in charge of filling up all the arguments of the LHS, how to deal with positional and keyword arguments without introducing new syntax?
My instinct is that we don't need to deal with that; that's what partial application is for. To be fair, I'd advocate better syntax for that, but it's another issue.
anyway, I guess it's pretty safe to assume that if fn_b() returns a scalar, it'll be easy to assume it's just a single positional argument.
print XYZ some_func XYZ another_func("Hello")
[...]
Meaning that the above could also be written as:
print XYZ some_func XYZ another_func XYZ "Hello"
That looks good to me, but I think another_func("Hello") is the better one to recommend. I think it makes it slightly more obvious what is going on.
Then the basic operator definition could be done with a dunder looking like: [...]
I think the special-casiness here is unfortunate and would cause problems. a(b()) doesn't randomly pass kwargs to a if b happens to return a certain kind of thing.
The title is misleading : it should be "nesting function calls"
Elazar
בתאריך שבת, 28 בינו' 2017, 18:38, מאת Ed Kellett
On Sat, 28 Jan 2017 at 14:27 zmo via Python-ideas
wrote: I agree this would look a bit more elegant. To focus on the feature of that operator, instead of how to write it, I'll use XYZ instead of <| in this post.
My thoughts exactly :)
So, considering it's decided that the RHS is in charge of filling up all the arguments of the LHS, how to deal with positional and keyword arguments without introducing new syntax?
My instinct is that we don't need to deal with that; that's what partial application is for. To be fair, I'd advocate better syntax for that, but it's another issue.
anyway, I guess it's pretty safe to assume that if fn_b() returns a scalar, it'll be easy to assume it's just a single positional argument.
print XYZ some_func XYZ another_func("Hello")
[...]
Meaning that the above could also be written as:
print XYZ some_func XYZ another_func XYZ "Hello"
That looks good to me, but I think another_func("Hello") is the better one to recommend. I think it makes it slightly more obvious what is going on.
Then the basic operator definition could be done with a dunder
looking like: [...]
I think the special-casiness here is unfortunate and would cause problems. a(b()) doesn't randomly pass kwargs to a if b happens to return a certain kind of thing. _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Sat, Jan 28, 2017 at 03:16:27PM +0100, zmo via Python-ideas wrote:
Hi list o/
This idea sounds fun, so as a thought experiment why not imagine one way of integrating it in what I believe would be pythonic enough.
This idea is sometimes called "the Collection Pipeline" design pattern, and is used in various command shells. Martin Fowler wrote about this design pattern here: https://martinfowler.com/articles/collection-pipeline/ and I wrote a recipe for it: https://code.activestate.com/recipes/580625-collection-pipeline-in-python/ with a working, although basic, implementation. The recipe shows that we don't need new syntax for this sort of feature. I'm rather partial to either the | or >> operators, both of which are rarely used except by ints. Nor does it need to be a built-in part of the language. It could be a third-party module, or a library module. I think that the most important feature of pipeline syntax is that we write the functions in the same order that they are applied, instead of backwards. Instead of: print(list(map(float, filter(lambda n: 20 < n < 30, data)))) where you have to read all the way to the right to find out what you are operating on, and then read backwards to the left in order to follow the execution order, a pipeline starts with the argument and then applies the functions in execution order: data | Filter(lambda n: 20 < n < 30) | Map(float) | List | Print (In principle, Python built-ins could support this sort of syntax so I could write filter, map, list, print rather than custom versions Filter, Map, etc. That would feel very natural to a language like Haskell, for example, where partial function application is a fundamental part of the language. But for Python that would be a *major* change, and not one I wish to propose. Easier to just have a separate, parallel set of pipeline functions, with an easy way to create new ones. A module is perfect for that.) Now we can see that these sorts of pipelines are best suited for a particular style of programming. It doesn't work so well for arbitrary function calls where the data arg could end up in any argument position: aardvark(1, 2, cheese('a', eggs(spam(arg), 'b')), 4) But I don't see that as a problem. This is not a replacement for regular function call syntax in its full generality, but a powerful design pattern for solving certain kinds of problems.
On Sat, Jan 28, 2017 at 12:41:24PM +0000, Ed Kellett wrote:
FWIW, I'd spell it without the (), so it's simply a right-associative binary operator on expressions, (a -> b, a) -> b, rather than magic syntax. print XYZ some_func XYZ another_func("Hello")
I agree this would look a bit more elegant. To focus on the feature of that operator, instead of how to write it, I'll use XYZ instead of <| in this post.
So, considering it's decided that the RHS is in charge of filling up all the arguments of the LHS,
Is that actually decided? That seems to break the advantage of a pipeline: the left-to-right order. To understand your syntax, you have to read from the right backwards to the left: # print(list(map(float, filter(lambda n: 20 < n < 30, data)))) print XYZ list XYZ map(float) XYZ filter(lambda n: 20 < n < 30, data) That's actually longer than the current syntax. Actually, I don't think this would work using your idea. filter would need to pass on *all* of map's arguments, not just the data argument: filter(float, lambda n: 20 < n < 30, data,) # returns a tuple (float, FilterObject) which gives us: print XYZ list XYZ map XYZ filter(float, lambda n: 20 < n < 30, data) But of course filter doesn't actually have that syntax, so either we have a new, parallel series of functions including Filter(...) or we write something like: print XYZ list XYZ map XYZ lambda (f1, f2, arg): (f1, filter(f2, arg))(float, lambda n: 20 < n < 30, data) which is simply horrid. Maybe there could be a series of helper functions, but I don't think this idea is workable. See below.
how to deal with positional and keyword arguments without introducing new syntax? Should it be by returning a tuple of positional iterable and keyword dict? i.e.:
def fn_a(*args, **kwarg): print("args: {}, kwarg: {}".format(args, kwarg))
def fn_b(): return (1,2,3), {'a':1, 'b':2, 'c':3}
fn_a XYZ fn_b()
The problem is that each function needs to know what arguments the *next* function expects. That means that the function on the right needs to have every argument used by the entire pipeline, and each function has to take the arguments it needs and pass on the rest. It also means that everything is very sensitive to the order that arguments are expected: def spam(func, data): ... def ham(argument, function): ... spam XYZ foo(bar, data) ham XYZ foo(bar, data) What should foo() return? [...]
In practice, such a scheme would make it possible to have:
print XYZ (("Hello World",), {"file": sys.stderr})
In what way is this even close to an improvement over the existing function call syntax? print XYZ (("Hello World",), {"file": sys.stderr}) print("Hello World", file=sys.stderr) If "Hello World" wasn't a literal, but came from somewhere else: print XYZ ((greetings(),), {"file": sys.stderr}) print(greetings(), file=sys.stderr) so you're not even avoiding nested parentheses.
All in all, it can be a nice syntactic sugar to have which could make it more flexible working with higher order functions, but it with the way I'm suggesting to comply with python's arguments handling, it offers little advantages when the RHS is not filling LHS arguments:
>>> print(all(map(lambda x: x>2, filter(lambda x: isinstance(x, int), range(0,3))))) True
vs
>>> print XYZ all XYZ map XYZ (lambda x: x>2, filter(lambda x: isinstance(x, int), range(0,3))), True
I think that "literal advantage" is being very kind. The best you can say is that you save two pairs of parentheses at the cost of three operators and moving arguments away from the functions that use them.
Here, applying map onto all onto print offers a great readability,
I don't think so. At *best*, it is no better than what we already have: print XYZ all XYZ map XYZ ... print ( all ( map ( ... but moving the arguments away from where they are used makes it unspeakable. Consider: def double(values): for v in values: return 2*v print(max(map(float, double(range(5))))) How would I use your syntax? print XYZ max XYZ map float XYZ double XYZ range XYZ 5 doesn't work without new syntax, and print XYZ max XYZ map XYZ double XYZ range XYZ (float, 5) doesn't work without re-writing range and double to pass on unused arguments. I'd need partial application: from functools import partial print XYZ max XYZ partial(map, float) XYZ double XYZ range XYZ 5 which is now starting to look like a collection pipeline written out backwards: 5 | Range | Apply(double) | Map(float) | Max | Print where (again) the Capital letter functions will be pipe-compatible versions of the usual range, map, etc. They don't necessarily have to be prepared before hand: many could be a simple wrapper around the built-in: Max = Apply(max) There may be ways to avoid even that. A third-party library is a good place to experiment with these questions, this is in no way ready for the standard library, let alone a new operator. [...]
But then it would be just another way to introduce currying as a language feature with an operator, so we should then just discuss on how to add currying as a language syntax "by the book", but I'm pretty sure that's a topic already discussed before I joined this list ;-)
The easiest way to support currying, or at least some form of it, is: from functools import partial as p p(map, float) # curries map with a single argument float which is not quite the map(float) syntax Haskell programmers expect, but its not awful. -- Steve
tl;dr: I agree with you, Steven, as proven by my former post, augmented with the details of your reply: there's no advantage to add a new operator and language construct for this use case.— On Sun, Jan 29, 2017 at 01:30:13PM +1100, Steven D'Aprano wrote:
On Sat, Jan 28, 2017 at 03:16:27PM +0100, zmo via Python-ideas wrote:
This idea sounds fun, so as a thought experiment why not imagine one way of integrating it in what I believe would be pythonic enough.
This idea is sometimes called "the Collection Pipeline" design pattern, and is used in various command shells. Martin Fowler wrote about this design pattern here: https://martinfowler.com/articles/collection-pipeline/ and I wrote a recipe for it: https://code.activestate.com/recipes/580625-collection-pipeline-in-python/ with a working, although basic, implementation. print(list(map(float, filter(lambda n: 20 < n < 30, data)))) […] data | Filter(lambda n: 20 < n < 30) | Map(float) | List | Print
It's indeed an interesting tip and idea, and using the pipe is not a bad idea as it's a good mnemonic for anyone who used a shell. About reading order, I'm personally agnostic.
(In principle, Python built-ins could support this sort of syntax so I could write filter, map, list, print rather than custom versions Filter, Map, etc. […] But for Python that would be a *major* change, and not one I wish to propose. […])
Even as an external library, I would use that kind of syntax with extreme care in python. As a python developer, one of the things I really do enjoy is that any python code looks like a python code, and that's because changing meaning of operators depending on the context is discouraged. Then, unlike Scala, C++ or Ruby, you never end up with the language looking like a new DSL for each application or framework.
On Sat, Jan 28, 2017 at 12:41:24PM +0000, Ed Kellett wrote: So, considering it's decided that the RHS is in charge of filling up all the arguments of the LHS, Is that actually decided?
it's not, it's part of the thought experiment of 'if we had such syntax', how could we handle arguments?
[…] so either we have a new, parallel series of functions including Filter(...) or we write something like: print XYZ list XYZ map XYZ lambda (f1, f2, arg): (f1, filter(f2, arg))(float, lambda n: 20 < n < 30, data) which is simply horrid. Maybe there could be a series of helper functions, but I don't think this idea is workable. […]
[…] All in all, it can be a nice syntactic sugar to have which could make it more flexible working with higher order functions, but it with the way I'm suggesting to comply with python's arguments handling, it offers little advantages when the RHS is not filling LHS arguments: […] I think that "literal advantage" is being very kind. The best you can say is that you save two pairs of parentheses at the cost of three operators and moving arguments away from the functions that use them.
I said "little" not "literal" ☺ I started the whole reasoning trying to be objective and figure how such a new syntax would be integrated in python and what good use could be made of it. And in the end, I end up with something that can offer a nice syntax for a very niche case, and wouldn't be of much use most of the time. The fact that it can be implemented with some operator overload, as you nicely demonstrated just proves the fact further: this is not a good idea.
[...]
But then it would be just another way to introduce currying as a language feature with an operator, so we should then just discuss on how to add currying as a language syntax "by the book", but I'm pretty sure that's a topic already discussed before I joined this list ;-) The easiest way to support currying, or at least some form of it, is: from functools import partial as p p(map, float) # curries map with a single argument float which is not quite the map(float) syntax Haskell programmers expect, but its not awful.
Indeed, I love having that available as a function! We could reopen the debate as to whether we should implement currying into python, but since my last post I've done a bit of searching, and found out it's been discussed 14 years ago: https://mail.python.org/pipermail/python-dev/2004-February/042668.html https://www.python.org/dev/peps/pep-0309/ and a few discussions, implementations of (real) currying published more recently: https://mtomassoli.wordpress.com/2012/03/18/currying-in-python/ http://code.activestate.com/recipes/577928-indefinite-currying-decorator-wit... https://gist.github.com/JulienPalard/021f1c7332507d6a494b I could argue that a nicer syntactic sugar and having it as a language feature could help in having it supported in a more optimised fashion, instead of using an added layer of abstraction. But, I won't ^^ Cheers, -- zmo
The `tools` (and `cytoolz` that has an identical API) provides an `@curry` decorator that is more general and elegant than the links earlier IMO. Maybe I'm biased because I work with principal author Matt Rocklin, but toolz is really neat. See: http://toolz.readthedocs.io/en/latest/curry.html On Sun, Jan 29, 2017 at 3:08 AM, zmo via Python-ideas < python-ideas@python.org> wrote:
tl;dr: I agree with you, Steven, as proven by my former post, augmented with the details of your reply: there's no advantage to add a new operator and language construct for this use case.—
On Sun, Jan 29, 2017 at 01:30:13PM +1100, Steven D'Aprano wrote:
On Sat, Jan 28, 2017 at 03:16:27PM +0100, zmo via Python-ideas wrote:
This idea sounds fun, so as a thought experiment why not imagine one way of integrating it in what I believe would be pythonic enough.
This idea is sometimes called "the Collection Pipeline" design pattern, and is used in various command shells. Martin Fowler wrote about this design pattern here: https://martinfowler.com/articles/collection-pipeline/ and I wrote a recipe for it: https://code.activestate.com/recipes/580625-collection- pipeline-in-python/ with a working, although basic, implementation. print(list(map(float, filter(lambda n: 20 < n < 30, data)))) […] data | Filter(lambda n: 20 < n < 30) | Map(float) | List | Print
It's indeed an interesting tip and idea, and using the pipe is not a bad idea as it's a good mnemonic for anyone who used a shell. About reading order, I'm personally agnostic.
(In principle, Python built-ins could support this sort of syntax so I could write filter, map, list, print rather than custom versions Filter, Map, etc. […] But for Python that would be a *major* change, and not one I wish to propose. […])
Even as an external library, I would use that kind of syntax with extreme care in python. As a python developer, one of the things I really do enjoy is that any python code looks like a python code, and that's because changing meaning of operators depending on the context is discouraged.
Then, unlike Scala, C++ or Ruby, you never end up with the language looking like a new DSL for each application or framework.
On Sat, Jan 28, 2017 at 12:41:24PM +0000, Ed Kellett wrote: So, considering it's decided that the RHS is in charge of filling up all the arguments of the LHS, Is that actually decided?
it's not, it's part of the thought experiment of 'if we had such syntax', how could we handle arguments?
[…] so either we have a new, parallel series of functions including Filter(...) or we write something like: print XYZ list XYZ map XYZ lambda (f1, f2, arg): (f1, filter(f2, arg))(float, lambda n: 20 < n < 30, data) which is simply horrid. Maybe there could be a series of helper functions, but I don't think this idea is workable. […]
[…] All in all, it can be a nice syntactic sugar to have which could make it more flexible working with higher order functions, but it with the way I'm suggesting to comply with python's arguments handling, it offers little advantages when the RHS is not filling LHS arguments: […] I think that "literal advantage" is being very kind. The best you can say is that you save two pairs of parentheses at the cost of three operators and moving arguments away from the functions that use them.
I said "little" not "literal" ☺ I started the whole reasoning trying to be objective and figure how such a new syntax would be integrated in python and what good use could be made of it. And in the end, I end up with something that can offer a nice syntax for a very niche case, and wouldn't be of much use most of the time.
The fact that it can be implemented with some operator overload, as you nicely demonstrated just proves the fact further: this is not a good idea.
[...]
But then it would be just another way to introduce currying as a language feature with an operator, so we should then just discuss on how to add currying as a language syntax "by the book", but I'm pretty sure that's a topic already discussed before I joined this list ;-) The easiest way to support currying, or at least some form of it, is: from functools import partial as p p(map, float) # curries map with a single argument float which is not quite the map(float) syntax Haskell programmers expect, but its not awful.
Indeed, I love having that available as a function! We could reopen the debate as to whether we should implement currying into python, but since my last post I've done a bit of searching, and found out it's been discussed 14 years ago:
https://mail.python.org/pipermail/python-dev/2004-February/042668.html https://www.python.org/dev/peps/pep-0309/
and a few discussions, implementations of (real) currying published more recently:
https://mtomassoli.wordpress.com/2012/03/18/currying-in-python/ http://code.activestate.com/recipes/577928-indefinite- currying-decorator-with-greedy-call-and/ https://gist.github.com/JulienPalard/021f1c7332507d6a494b
I could argue that a nicer syntactic sugar and having it as a language feature could help in having it supported in a more optimised fashion, instead of using an added layer of abstraction. But, I won't ^^
Cheers,
-- zmo _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.
Just for one mor eexample - I have a toy implementation here as well:
https://github.com/jsbueno/chillicurry
I decided to use the "." operator itself, and frame introspection to
retrieve the function
to be called from the calling context - that is rude, I know.
But what I like of this approach is that by using the "." I have full
control of the objects called as functions
on the chain - which allowed me to use a constant value that will be
replaced by the "dynamic" parameter from
previous function calls.
So, if I want "max(len(mytext), 10)", I can write "curry.max(DELAY,
10).len(mytext)" - the "max" call will only take place after len is
evaluated. (And for functions wth a single parameter, there is no need for
that)
Lessons learned:
1) One wanting curry can do so without changing Python Syntax
2) For control of functions that need more than one parameter, one needs a
lazy-call mechanism.
Possibly the "lazy call" mechanism would be a more interesting "add on" to
the language than currying per se. (It is
due to not being able to lazy call that I resort to the transforms using
".")
That said, if anyone like the the "chillicurry" approach enough that wants
to help polishing it enough for pypi,
just get in touch.
js
-><-
On 29 January 2017 at 18:38, David Mertz
The `tools` (and `cytoolz` that has an identical API) provides an `@curry` decorator that is more general and elegant than the links earlier IMO. Maybe I'm biased because I work with principal author Matt Rocklin, but toolz is really neat.
See: http://toolz.readthedocs.io/en/latest/curry.html
On Sun, Jan 29, 2017 at 3:08 AM, zmo via Python-ideas < python-ideas@python.org> wrote:
tl;dr: I agree with you, Steven, as proven by my former post, augmented with the details of your reply: there's no advantage to add a new operator and language construct for this use case.—
On Sun, Jan 29, 2017 at 01:30:13PM +1100, Steven D'Aprano wrote:
On Sat, Jan 28, 2017 at 03:16:27PM +0100, zmo via Python-ideas wrote:
This idea sounds fun, so as a thought experiment why not imagine one way of integrating it in what I believe would be pythonic enough.
This idea is sometimes called "the Collection Pipeline" design pattern, and is used in various command shells. Martin Fowler wrote about this design pattern here: https://martinfowler.com/articles/collection-pipeline/ and I wrote a recipe for it: https://code.activestate.com/recipes/580625-collection-pipel ine-in-python/ with a working, although basic, implementation. print(list(map(float, filter(lambda n: 20 < n < 30, data)))) […] data | Filter(lambda n: 20 < n < 30) | Map(float) | List | Print
It's indeed an interesting tip and idea, and using the pipe is not a bad idea as it's a good mnemonic for anyone who used a shell. About reading order, I'm personally agnostic.
(In principle, Python built-ins could support this sort of syntax so I could write filter, map, list, print rather than custom versions Filter, Map, etc. […] But for Python that would be a *major* change, and not one I wish to propose. […])
Even as an external library, I would use that kind of syntax with extreme care in python. As a python developer, one of the things I really do enjoy is that any python code looks like a python code, and that's because changing meaning of operators depending on the context is discouraged.
Then, unlike Scala, C++ or Ruby, you never end up with the language looking like a new DSL for each application or framework.
On Sat, Jan 28, 2017 at 12:41:24PM +0000, Ed Kellett wrote: So, considering it's decided that the RHS is in charge of filling up all the arguments of the LHS, Is that actually decided?
it's not, it's part of the thought experiment of 'if we had such syntax', how could we handle arguments?
[…] so either we have a new, parallel series of functions including Filter(...) or we write something like: print XYZ list XYZ map XYZ lambda (f1, f2, arg): (f1, filter(f2, arg))(float, lambda n: 20 < n < 30, data) which is simply horrid. Maybe there could be a series of helper functions, but I don't think this idea is workable. […]
[…] All in all, it can be a nice syntactic sugar to have which could make it more flexible working with higher order functions, but it with the way I'm suggesting to comply with python's arguments handling, it offers little advantages when the RHS is not filling LHS arguments: […] I think that "literal advantage" is being very kind. The best you can say is that you save two pairs of parentheses at the cost of three operators and moving arguments away from the functions that use them.
I said "little" not "literal" ☺ I started the whole reasoning trying to be objective and figure how such a new syntax would be integrated in python and what good use could be made of it. And in the end, I end up with something that can offer a nice syntax for a very niche case, and wouldn't be of much use most of the time.
The fact that it can be implemented with some operator overload, as you nicely demonstrated just proves the fact further: this is not a good idea.
[...]
But then it would be just another way to introduce currying as a language feature with an operator, so we should then just discuss on how to add currying as a language syntax "by the book", but I'm pretty sure that's a topic already discussed before I joined this list ;-) The easiest way to support currying, or at least some form of it, is: from functools import partial as p p(map, float) # curries map with a single argument float which is not quite the map(float) syntax Haskell programmers expect, but its not awful.
Indeed, I love having that available as a function! We could reopen the debate as to whether we should implement currying into python, but since my last post I've done a bit of searching, and found out it's been discussed 14 years ago:
https://mail.python.org/pipermail/python-dev/2004-February/042668.html https://www.python.org/dev/peps/pep-0309/
and a few discussions, implementations of (real) currying published more recently:
https://mtomassoli.wordpress.com/2012/03/18/currying-in-python/ http://code.activestate.com/recipes/577928-indefinite-curryi ng-decorator-with-greedy-call-and/ https://gist.github.com/JulienPalard/021f1c7332507d6a494b
I could argue that a nicer syntactic sugar and having it as a language feature could help in having it supported in a more optimised fashion, instead of using an added layer of abstraction. But, I won't ^^
Cheers,
-- zmo _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On 27 January 2017 at 22:07, Brent Brinkley
HI Everyone,
One issue that I’ve seen in a lot of languages struggle with is nested function calls.
Parenthesis when nested inherently create readability issues.
Yes there is such issue. I don't see however that a radical change to nested notation can be a solution here. Not because it is too hard to find a consensus about how it should be, but also because in some sense the parenthesis nesting is sort of optimal for general case. It is hard to explain in simple words, but some of the answers gave hints already -- if you try some more complex nesting with your proposed example, it will end up with even *worse* schemas than parenthesis notation. One important note about parenthesis itself: often it looks so bad simply because in monospaced font, e.g. Courier, parenthesis is sort of "slightly bent letter I" which results in really bad look, and the *correct* parenthesis character is a rounded bracket, which extends to bottom ant top much further than letters. It has thin endpoints and thicker middle. And it should be given some space from left and right. So a big part of the problem lies in the font. As for the problem of long equations. Here could be many proposals. My opinion: nested equations must be broken into series of smaller ones, but unfortunately Python does not provide a standard solution here. *Theoretically* I see a solution by 'inlined' statements. Take a long example: print ( merge (a, b, merge ( long_variable2, long_variable2 ) ) Now just split it in 2 lines: tmp <> merge ( long_variable2, long_variable2 ) print ( merge (a, b, tmp ) ) So I'd for example invent a special sign which just marks statements that will be first collected as inline text, sort of macros. But as always, in such cases there is little chance to find any consensus due to many reasons. One of the reasons that there are too few good looking characters out there, and same applies for any possible improvement. Mikhail
On Mon, Jan 30, 2017 at 11:52 AM, Mikhail V
*Theoretically* I see a solution by 'inlined' statements. Take a long example:
print ( merge (a, b, merge ( long_variable2, long_variable2 ) )
Now just split it in 2 lines:
tmp <> merge ( long_variable2, long_variable2 ) print ( merge (a, b, tmp ) )
So I'd for example invent a special sign which just marks statements that will be first collected as inline text, sort of macros.
I have a great idea for this special sign. We could use the equal sign '=' for this purpose of assigning a value into a temporary name. :-) tmp = merge(long_variable2, long_variable2) print (merge(a, b, tmp) ) -- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.
On 30 January 2017 at 21:25, David Mertz
On Mon, Jan 30, 2017 at 11:52 AM, Mikhail V
wrote: *Theoretically* I see a solution by 'inlined' statements. Take a long example:
print ( merge (a, b, merge ( long_variable2, long_variable2 ) )
Now just split it in 2 lines:
tmp <> merge ( long_variable2, long_variable2 ) print ( merge (a, b, tmp ) )
So I'd for example invent a special sign which just marks statements that will be first collected as inline text, sort of macros.
I have a great idea for this special sign. We could use the equal sign '=' for this purpose of assigning a value into a temporary name. :-)
tmp = merge(long_variable2, long_variable2) print (merge(a, b, tmp) )
Great idea :) But actually that was my idea initially, so just breaking it into two lines solves the readability issue perfectly with long expressions. Although if one is chasing some kind of optimisations... I don't know, I see very often people want to stick everything in one big expression.
On 31.01.2017 00:54, Mikhail V wrote:
Great idea :) But actually that was my idea initially, so just breaking it into two lines solves the readability issue perfectly with long expressions. Although if one is chasing some kind of optimisations... I don't know, I see very often people want to stick everything in one big expression.
Because it's natural. It's *sometimes* the best way to convey the data processing pipeline. It's the connection between separate parts that needs to be conveyed. Furthermore, inventing artificial names is sometimes not the best way. So, I think the behavior you've described can be explained quite easily. Best, Sven
participants (13)
-
Brent Brinkley
-
C Anthony Risinger
-
Chris Angelico
-
David Mertz
-
Ed Kellett
-
Ethan Furman
-
Joao S. O. Bueno
-
João Santos
-
Mikhail V
-
Steven D'Aprano
-
Sven R. Kunze
-
zmo
-
אלעזר