len(path) == len(str(path))

Hi everyone, I submitted a PR today, and Serhiy decided it needs a discussion on python-ideas and agreement from core developers before it could go forward. BPO: https://bugs.python.org/issue40752 PR: https://github.com/python/cpython/pull/20348 Today I wrote a script and did this: sorted(paths, key=lambda path: len(str(path)), reverse=True) But it would have been nicer if I could do this: sorted(paths, key=len, reverse=True) So I implemented `PurePath.__len__` as `str(len(path))`. Serhiy and Remi objected, because it might not be obvious that the length of the path would be the length of string. What do you think? Can I get some +1s and -1s for this change? Thanks, Ram.

"So I implemented `PurePath.__len__` as `str(len(path))`." Sure you meant len(str(path)), right? "Serhiy and Remi objected, because it might not be obvious that the length of the path would be the length of string." I find this _really_ unintuitive. If anything, I would expect len(p) to be the "depth" of the path, which doesn't make a lot of sense if it is not an absolute path. It is a -1 from me because, besides what I outlined above, if you need the length of the string representation, len(str(p)) is quite short and obvious already.

On Sun, May 24, 2020 at 1:42 PM Bernardo Sulzbach < bernardo@bernardosulzbach.com> wrote:
I agree. I expect iteration, indexing, and length to refer to parts, not characters. In particular I'm a bit disappointed that `path[-1]` isn't equal to `path.name`, especially because finding `path.name` was tricky - at first I guessed path.basename (doesn't exist) and path.stem (close, but wrong). Can we implement that instead?

On Sun, May 24, 2020 at 4:59 AM Alex Hall <alex.mojaki@gmail.com> wrote:
+1 on that! I can't say I have better names to suggest, but I find it remarkably difficult to figure out how to get the parts of a Path that I want path[:-1] would really hand, too, and presumably come along with any indexing. -CHB -- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

Hello, On Sun, 24 May 2020 08:38:22 -0300 Bernardo Sulzbach <bernardo@bernardosulzbach.com> wrote:
Rather, number of components in a path. E.g. length of Path("../a") is 2.
I fully agree. For people who find string representation of paths to be nice, there's an obvious choice of not using pathlib in the first place, no confusion ensues. And people who choose to use pathlib, should not look for ways to cheat by conflating strings and Path objects with confusing implicit conversions (which will bite people who will need to work with such a code later). -- Best regards, Paul mailto:pmiscml@gmail.com

On 24.05.2020 13:27, Ram Rachum wrote:
It would be surprising to have an object which implements .__len__(), but otherwise doesn't allow any indexing, so -1 on such a change. If Paths ever get indexing, it would also be more natural to have those indexes refer to path components. The .__len__() would have to map to the number of path components, not the length of a string representation. -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, May 24 2020)
Python Projects, Coaching and Support ... https://www.egenix.com/ Python Product Development ... https://consulting.egenix.com/
::: We implement business ideas - efficiently in both time and costs ::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 https://www.egenix.com/company/contact/ https://www.malemburg.com/

On 24May2020 13:46, M.-A. Lemburg <mal@egenix.com> wrote:
It would be surprising to have an object which implements .__len__(), but otherwise doesn't allow any indexing, so -1 on such a change.
set() ? I personally don't have a fundamental problem with something having a size but no indexing.
If Paths ever get indexing, it would also be more natural to have those indexes refer to path components.
Aye, I think the same.
Yes, it would be confusing for len() to measure something different to that measured by a numeric index. I think this makes me -1 on the proposal, also because str(Path) is so easy to do. Cheers, Cameron Simpson <cs@cskk.id.au>

On 25.05.2020 03:34, Cameron Simpson wrote:
Good point. Thinking about it, my intuition wasn't fully correct: there are certainly container object types which do have a size, but don't allow direct position based access to their elements, e.g. ones which only allow iteration. Cheers, -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, May 25 2020)
Python Projects, Coaching and Support ... https://www.egenix.com/ Python Product Development ... https://consulting.egenix.com/
::: We implement business ideas - efficiently in both time and costs ::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 https://www.egenix.com/company/contact/ https://www.malemburg.com/

On 5/24/20 7:27 AM, Ram Rachum wrote:
While the length of the string representation might be one definition of length, so might the 'depth' (how many directory levels down does it go), and that might be considered more fundamental, and thus a better choice if anything was defined as its 'length' (which I am not sure it should). Length (as you are using it) is really a sequence like property, and paths are that much like a sequence. -- Richard Damon

On Sun, May 24, 2020 at 1:58 PM Ram Rachum <ram@rachum.com> wrote:
I know, that's why I said parts. But remembering or looking that up is not easy either, so being able to skip that and just do things like index directly could be useful. On the other hand, str(path) is pretty essential knowledge, there's no point in helping people not look that up.

On Sun, May 24, 2020 at 02:27:00PM +0300, Ram Rachum wrote:
It would have been even nicer if we could compose functions: sorted(paths, key=len∘str, reverse=True) *semi-wink*
It isn't obvious to me that the length of a path is the length of the string representation of that path. I thought that the length of the path would be the number of path components: /tmp/file # length 2 I wouldn't have predicted that was the length of the string representation of the path. -- Steven

Hi Steven, On Sun, May 24, 2020, 8:14 AM Steven D'Aprano
sorted(paths, key=len∘str, reverse=True) *semi-wink*
Do you have an evil twin with whom you share email. Yesterday you were arguing against functional programming style on the grounds that filtering based on a predicate was too obscure represented as 'filter(pred, ...))' Today you want full function composition. Fwiw, I like function composition. This example makes a good case for it. Too bad that symbol isn't on most keyboards. I don't think 'compose(len, str)' looks so terrible, but it's longer.

Hello, On Sun, 24 May 2020 08:23:29 -0400 David Mertz <mertz@gnosis.cx> wrote:
Could find a use for that @ operator, finally. Note to myself: should implement that for my pycopy-dev dialect (https://github.com/pfalcon/pycopy). Currently it allows to override methods on (selected) builtin types, but not operators. Need to put that on TODO. -- Best regards, Paul mailto:pmiscml@gmail.com

On Sunday, May 24, 2020, at 08:07 -0400, Steven D'Aprano wrote:
It started with y = len(str(f(g(h(x))))), which is ugly. Some people like pipes, and wrote object-like functions that could be composed with the "|" character slash: y = x | h | g | f | str | len (Or something like that. Maybe there was a ">" at the beginning.) Then a clojure fan I know showed me function called "thread": f = thread([len, str, f, g, h]) y = f(x) An untested Python implementation: def thread(fs): # or maybe you like def thread(*fs) instead fs = reversed(fs) # or not, depending on how fs was constructed def inner(x): for f in fs: x = f(x) return x return inner sorted(paths, key=thread([len, str]), reverse=True) On the one hand, it's not quite as concise as composing the functions directly. On the other hand, ∘ ruffles a lot of ASCII feathers (but I'm sure Steven knows that).

What's wrong with using @? If I understand correctly, it's used for matrix multiplication, which is far enough from function composition to avoid confusion. And it's slightly similar visually to a circle. On Sun, May 24, 2020 at 4:25 PM Dan Sommers < 2QdxY4RzWzUUiLuE@potatochowder.com> wrote:

On Sun, May 24, 2020 at 3:38 PM Ram Rachum <ram@rachum.com> wrote:
During discussions of this PEP, a similar suggestion was made to define @ as a general purpose function composition operator, and this suffers from
The matrix multiplication PEP (https://www.python.org/dev/peps/pep-0465/) says: the same problem; functools.compose isn't even useful enough to exist. That particular sentiment seems to come from here: https://mail.python.org/archives/list/python-ideas@python.org/message/JQQ6IV... More generally, the discussion is easy to search for: https://mail.python.org/archives/search?mlist=python-ideas%40python.org&q=matrix+function+composition

Changed subject line. This is far from original topic. On Sun, May 24, 2020, 9:35 AM Ram Rachum <ram@rachum.com> wrote:
I like @. Function composition is kinda-sorta enough like for product that I can even make sense of the same operator. But how would you go about getting a .__matmul__ attribute onto all functions. For ones you write yourselves, you could decorate them at definition. What about all the other functions though. I suppose this is possible: @compasable def foo(x): return x + "foo" len = composable(len) newfun = len @ foo @ str.upper nbar = newfun("bar") # 6 I deliberately didn't decorate str.upper in the example since it shouldn't matter for a left-associative operator.

On Sun, May 24, 2020 at 10:49:45AM -0400, David Mertz wrote:
As a functional programming fan, you might not know about this, but we object oriented programming people have this concept called "inheritance" where we would add the attribute to FunctionType once, and just like magic every function would support it! *wink* Steven (the evil twin) P.S. sadly, operator dunders are looked up on the class, not the instance, so adding a `__matmul__` method to individual functions doesn't help. It would have to be done at the class.

On 25/05/20 3:27 am, Ram Rachum wrote:
Speaking of evil, another evil idea would be to write code that modifies the ast and changes @ between functions to call a compose function instead.
How do you tell just from the ast whether a particular @ is between functions? -- Greg

On Sun, May 24, 2020 at 11:21 AM Steven D'Aprano <steve@pearwood.info> wrote:
I think I heard of that inheritance thing somewhere! :-) My thought is really that there's no way we're going to get .__matmul__ added (with the meaning "compose") to FunctionType. So I was thinking about what one could do with a separate module. I.e. something that could live on PyPI... before, perhaps someday, being added to standard library. Is there an evil way to monkey patch FunctionType? I.e. without actually recompiling a fork of the interpreter. Alex' forbiddenfruit.curse is pretty cool looking. But it does require explicitly cursing each individual function that might be composed. Alex Hall:
I think once you compose three or more functions, lambda starts to look pretty bad. This is only slightly mitigated by one of those proposals for "a more concise lambda" that occur intermittently. And if you actually save a composed function under a meaningful name for later use, that much more so. Of course, I'm really not all that bothered by `my_op = compose(fun1, fun2, fun3, fun4)`. The @ might be cool, but writing my own HoF compose() isn't hard. I've been playing more lately with R Tidyverse. It's pipe with currying of first argument is actually really nice. The pipe operator is god-awful ugly. But other than that it works nicely. For example: iris %>% group_by(Species) %>% summarize_if(is.numeric, mean) %>% ungroup() %>% gather(measure, value, -Species) %>% arrange(value) It's not abstract composition since it always starts with a concrete object the several operations work on. But it is some of the same feel. -- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions.

On Sun, May 24, 2020 at 6:56 PM David Mertz <mertz@gnosis.cx> wrote:
forbiddenfruit patches the type, look again. I used a loop because there isn't just one FunctionType: `type(compose) != type(len)`.
It does? How? It sounds like the problem isn't the syntax for lambda, but just function composition in general, maybe because of too many parentheses. Maybe you'd like to change: foo = bar(len(str(path))) into foo = (bar @ len @ str)(path) In any case, it's rare to be able to simply compose three unary functions, e.g. `sorted(paths, key=bar @ len @ str)`. Usually you need to do something slightly more complicated, e.g. a second argument somewhere, and if you throw in functools.partial I think that's easily worse.
Why is: my_op = bar @ len @ str even more of an improvement over: my_op = lambda p: bar(len(str(p))) than @ is an improvement in the previous examples? If the answer is that you're not supposed to assign lambdas, I think the same logic should dictate not assigning compositions. In fact, the reason to not assign lambdas is for better tracebacks, and now that I think of it, function composition would completely destroy tracebacks. For example: ``` for function in [ lambda p: str(len(p)), str @ len, ]: try: function(3) except: traceback.print_exc() ``` Output: ``` Traceback (most recent call last): File "main.py", line 15, in <module> function(3) File "main.py", line 11, in <lambda> lambda p: str(len(p)), TypeError: object of type 'int' has no len() Traceback (most recent call last): File "main.py", line 15, in <module> function(3) File "main.py", line 5, in <lambda> return lambda *args, **kwargs: self(other(*args, **kwargs)) TypeError: object of type 'int' has no len() ``` https://repl.it/@alexmojaki/LeanEnergeticPublisher-1 So based on that I'm strongly against this kind of proposal without syntactic support. I've been playing more lately with R Tidyverse. It's pipe with currying of

On Sun, May 24, 2020, 3:43 PM Guido van Rossum <guido@python.org> wrote:
I’ve never been able to remember whether (f@g)(x) means f(g(x)) or g(f(x)). That pretty much kills the idea for me.
Well, it means whichever one the designers decide it should mean. But obviously it's a thing to remember, and one that could sensibly go the other way. On the other hand, when I showed an example using filter() a couple days ago, I had to try it to remember whether the predicate or the iterable came first. Lots of such decisions are pretty arbitrary.

On Sun, May 24, 2020 at 10:53 PM David Mertz <mertz@gnosis.cx> wrote:
But when you *read* a call to filter(), it's generally pretty obvious which argument is which, even if you don't remember the signature. You just need to see which one's callable or which one's iterable (few things are both). You can probably guess just from the variable names. If you read `f@g`, you can only guess if one of `f(g(x))` or `g(f(x))` is nonsense, which I think is typically less obvious. Taking the `len@str` example, `str(len(path))` is a sensible expression, it's just a terrible sort key, and having to think that far sucks for readability. Also in the paths example, if you remember wrong and write the equivalent of `sorted(paths, key=lambda path: str(len(path)))`, you get a list in nonsense order which may be confusing to debug. If you get the order wrong in `filter()`, you should eventually get an error like `TypeError: 'list' object is not callable`.

On Sun, May 24, 2020, 5:11 PM Alex Hall
I definitely agree. My way of remembering which way filter goes was to try the wrong one in a shell and get an exception. In contrast, quite likely both f(g(h(x))) and h(g(f(x))) produce a value, but only one is the one I want. If x is string, or a list, or a number, or a NumPy array, most functions I'd call return something of the same type. Mutations are usually not order independent.

[Guido]
I’ve never been able to remember whether (f@g)(x) means f(g(x)) or g(f(x)). That pretty much kills the idea for me.
[David Mertz]
Best I know, f@g applies g first in every language that implements a composition operator, and in mathematics. While that may be arbitrary, it's easy to remember: (f@g)(x) "looks a heck of a lot more like" f(g(x)) than g(f(x)) because the former leaves the identifiers in the same order.

Dang, you're right. It works as you'd expect without overthinking it. :-) I guess what has always (seriously, since my undergrad years studying math!) confused me is that somehow when this function is introduced <https://en.wikipedia.org/wiki/Function_composition> they say (g*f)(x) = g(f(x)). The professor starts by showing f(x), and then shows how you can apply g() to the result, and lo, you have defined g*f. (I'm sorry, I refuse to learn how to type the symbol they actually use. :-) On Sun, May 24, 2020 at 2:17 PM Tim Peters <tim.peters@gmail.com> wrote:
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...>

Tiimmy:
On Sun, May 24, 2020 at 5:39 PM Guido van Rossum <guido@python.org> wrote:
Dang, you're right. It works as you'd expect without overthinking it. :-)
I think one thing that pulls in different directions is that both composition and piping are useful, and do something closely related. But in one the data "goes" forward and in the other the data "goes backward." I use bash a lot, and writing something like this is common: cat data | sort | cut -d; -f6 | grep ^foo | sort -r | uniq -c My data is moving left to right through the operations. In contrast, I might write in some hypothetical programming language: myfilter = uniq[count] ∘ sort[reverse] ∘ grep[^foo] ∘ cut[;,f6] ∘ sort result = myfilter(data) (In my just-now-invented PL, the square brackets are some kind of partial application operators) Here my data is moving right to left within the composition. Haskell spells pipes approximately `>>`, F# as `|>`, R as `%>%`, and so on. But some of the same languages also have composition (point-free style) that reverses the order of operations (using different symbols). -- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions.

On Sun, May 24, 2020 at 06:31:46PM -0400, David Mertz wrote:
The same rule applies to function application though.
I use bash a lot, and writing something like this is common:
cat data | sort | cut -d; -f6 | grep ^foo | sort -r | uniq -c
And today's "Useless Use Of cat Award" goes to... :-) sort data | ... (What is it specifically about cat that is so attractive? I almost certainly would have done exactly what you did, even knowing that sort will take a file argument.)
Compared to the regular old function call syntax: uniq(sort(grep(cut(sort(data))))) (ignoring extra arguments) where the data still moves right to left. On the third hand, even if your language doesn't have pipes, it may have methods: data.sort().cut().grep().sort().uniq() which now moves left to right again. -- Steven

On Mon, May 25, 2020 at 8:54 AM Steven D'Aprano <steve@pearwood.info> wrote:
<data sort | ... (When you use cat in a pipeline: https://http.cat/304 )
Maybe it's function call syntax that is the weird one. It might be familiar to us, but maybe it's the one that's actually causing the weirdness. Suppose we wrote the function last: (data)sort Now it's basically become a pipeline. And yet this syntax looks so utterly bizarre that none of us would accept it... ChrisA

On 24/05/2020 23:58, Chris Angelico wrote:
((path)str)len # First does something with 'path' and 'str', then does something with the result and `len'. # Not: first does something with `len' and ... what? Jesus, why is the whole world not using it already? Maybe for the same reason the world still uses QWERTY keyboards. Maybe that's what programmers (well, the smart ones:-)) in 2100 using Python 42 will use. Hell, it's currently illegal syntax. Maybe it could be introduced soon (would the new parser help? :-)) , in the hope that people will gradually fall out of the old usage `sort(data)`, so that eventually it can be deprecated and finally dropped (by 2100 or thereabouts). (Guido, are you listening?)

On Sun, May 24, 2020, 9:03 PM Rob Cliffe via Python-ideas
Forth was created in 1970. The idea isn't brand new. C is two years newer (1972). But Algol is 12 years older (1958). Most languages of the last 30 years seem to be influenced most by C, or at least the Algol tradition for basic syntax. That doesn't mean it's better, but it won out for whatever reason. Smalltalk (1972) is interestingly different from either the Forth or Algol style. But closer to Forth. Fwiw, I don't think I'd have much difficulty adjusting to the postfix calling style, but Python isn't going to do that, even in version 17.3 in 2100.

On Sun, May 24, 2020 at 6:56 PM Steven D'Aprano <steve@pearwood.info> wrote:
This is probably going afield since it is a bash thing, not a Python thing. But I can actually answer this quite specifically. When I write a pipeline like that, I usually do not do it in one pass. I write a couple of the stages, look at what I have, and then add some more stages until I get it right. Many of the commands in the pipeline can take a file argument (not just sort, also cut, also grep, also uniq... everything I used in the example). But I find fairly often that I need to add a step BEFORE what I initially thought was first processing step. And then I have to remove the filename as an argument of that no-longer-first step. Rinse and repeat. With `cat` I know it does nothing, and I won't have to change it later (well, OK, sometimes I want -n or -s). So it is a completely generic "data" object ... sort of like how I would write "fluent programming" starting with a Pandas DataFrame, for example, and calling chains of methods.. -- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions.

JavaScript has an early proposal for this use-case: https://github.com/tc39/proposal-partial-application#pipeline-and-partial-ap... where "cat data | sort | cut -d; -f6 | grep ^foo | sort -r | uniq -c" could be represented as: data |> sort |> cut(?, delimeter=";", fields=6) |> grep(?, "^foo") |> sort(?, reverse=True) |> uniq(?, count=True) A very similar operator already exists for Hack which has served to clean up a lot of code at Facebook: https://docs.hhvm.com/hack/expressions-and-operators/pipe ( fun fact: I was one of the people who asked for __matmul__ back in the day, but now I prefer the pipeline operator as it's not restricted to unary functions and is, IMHO, more readable https://mail.python.org/archives/list/python-ideas@python.org/message/I4DXFR... ) On Sun, May 24, 2020 at 7:33 PM David Mertz <mertz@gnosis.cx> wrote:

On 24May2020 19:28, David Mertz <mertz@gnosis.cx> wrote:
People forget that redirections may appear anywhere rather than just at the end of the command: <data sort | cut | ... >output But I take your point on incremental build-the-pipeline, I do that a lot. Cheers, Cameron Simpson <cs@cskk.id.au>

Here's another whacky idea: how about adding a so-called FuncSeq to the collections library for composing functions? Since FuncSeq is (obviously) a sequence, that might help with the intuition of the function application order. The idea is the function application order is the same as the iteration order of the sequence: from collections import FuncSeq fun_seq = FuncSeq([str, len]) fun_seq(789) # 3 fun_seq[::-1]("789") # '3' --- Ricky. "I've never met a Kentucky man who wasn't either thinking about going home or actually going home." - Happy Chandler

Really, I don't think new syntax is needed for that, as it is trivially resolved by a function call (I would type an example but Ricky's answer covers it) Moreover, such a callable, or other helper-callables can trivially enable ways of connecting the piped values through specific (positional/named) parameters in the next calls - allowing a whole Graph of functions to be composed. (And it is so easy to do and flexible extendable, I can't see a place for that in the stdlib) On Mon, 25 May 2020 at 00:11, Ricky Teachey <ricky@teachey.org> wrote:

On Sun, May 24, 2020 at 11:17 PM Tim Peters <tim.peters@gmail.com> wrote:
I personally find it easy to remember, but I can imagine (perhaps incorrectly) how others may find it difficult. There's a 'wrong' rule that's equally easy to remember: f@g means call f then call g. In fact, when you think of it like that, you might not even notice that this means g(f(x)). So even if you know that f@g means f(g(x)), you can still mess this up by not thinking it through. This is similar to when you have a list of decorators. Lots of people expect them to be called top to bottom ( https://stackoverflow.com/questions/27342149/decorator-execution-order) and it's not easy to make the real order feel more intuitive.

On Sun, May 24, 2020 at 4:55 PM David Mertz <mertz@gnosis.cx> wrote:
That's easy, especially when everyone is talking about evil. ``` from forbiddenfruit import curse def compose(self, other): return lambda *args, **kwargs: self(other (*args, **kwargs)) for func in [compose, len]: curse(type(func), "__matmul__", compose) assert (len @ str)(789) == 3 ``` https://repl.it/@alexmojaki/LeanEnergeticPublisher#main.py But seriously, I don't see that much point to this idea. It's just slightly more concise while not being particularly readable or beginner friendly. ``` sorted(paths, key=len @ str) sorted(paths, key=lambda p: len(str(p))) ``` I think the problem is just that lambda isn't the nicest syntax, and we'd like something more concise, e.g. ``` sorted(paths, key=:len(str($))) ``` which would be more generally useful. But I know this kind of thing has been discussed to death.

Dan Sommers writes:
On the other hand, ∘ ruffles a lot of ASCII feathers (but I'm sure Steven knows that).
On the gripping hand, "<>" looks more or less like a circle :-), and we could finally put barry_as_FLUFL __past__ us! ;-) Here's a wacky syntax idea: to get a partially applied function of one argument, substitute the composition symbol for the argument. So if you have def foo(file, data) and def bar(x, parms) -> data, you could compose them with composed = foo(file, <>) <> bar(<>, parms) (closing over file and parms), and then apply composed(x). Not sure about readability, especially with lots of arguments, or usability.

As we already, in the interpreter, use _ for last result, *vars for unpacking a list of positional variables and ** for unpacking to a dictionary how about (and this already messes up I18N if you are using _() as an alias for T() to mark strings for translation). We also currently use the syntax A().B() to do call method A and then call method B of the result How about: @piped A(params).B(_, options).C(*_).D(**_) As a reasonably recognisable (ASCII only) syntax where the @piped decorator tells us to call each function as soon as we have any returned value(s). The other familiar option, (for those used to the bash shell), would be to use a bare - as the marker for where to put the result(s) from the previous stage. Steve Barnes -----Original Message----- From: Stephen J. Turnbull <turnbull.stephen.fw@u.tsukuba.ac.jp> Sent: 25 May 2020 02:28 To: Dan Sommers <2QdxY4RzWzUUiLuE@potatochowder.com> Cc: python-ideas@python.org Subject: [Python-ideas] Re: len(path) == len(str(path)) Dan Sommers writes:
On the other hand, ∘ ruffles a lot of ASCII feathers (but I'm sure > Steven knows that).
On the gripping hand, "<>" looks more or less like a circle :-), and we could finally put barry_as_FLUFL __past__ us! ;-) Here's a wacky syntax idea: to get a partially applied function of one argument, substitute the composition symbol for the argument. So if you have def foo(file, data) and def bar(x, parms) -> data, you could compose them with composed = foo(file, <>) <> bar(<>, parms) (closing over file and parms), and then apply composed(x). Not sure about readability, especially with lots of arguments, or usability. _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/LVBKHR... Code of Conduct: http://python.org/psf/codeofconduct/

While I'm -1 on the original proposal, I think the idea of PurePath.__len__ returning the number of components in the path may be worth some further consideration. Also, I'm not convinced that having indexing is a necessary prerequisite to pursue it further. On Sun, May 24, 2020 at 8:14 AM Steven D'Aprano <steve@pearwood.info> wrote:

Kyle Stanley wrote:
FYI PEP 428 says: In this proposal, the path classes do not derive from a builtin type. This contrasts with some other Path class proposals which were derived from str. They also do not pretend to implement the sequence protocol: if you want a path to act as a sequence, you have to lookup a dedicated attribute (the parts attribute). So while __len__() is not the whole sequence protocol, it seems like the idea was to explicitely use `len(path.parts)` instead of `len(path)` for this.

"So I implemented `PurePath.__len__` as `str(len(path))`." Sure you meant len(str(path)), right? "Serhiy and Remi objected, because it might not be obvious that the length of the path would be the length of string." I find this _really_ unintuitive. If anything, I would expect len(p) to be the "depth" of the path, which doesn't make a lot of sense if it is not an absolute path. It is a -1 from me because, besides what I outlined above, if you need the length of the string representation, len(str(p)) is quite short and obvious already.

On Sun, May 24, 2020 at 1:42 PM Bernardo Sulzbach < bernardo@bernardosulzbach.com> wrote:
I agree. I expect iteration, indexing, and length to refer to parts, not characters. In particular I'm a bit disappointed that `path[-1]` isn't equal to `path.name`, especially because finding `path.name` was tricky - at first I guessed path.basename (doesn't exist) and path.stem (close, but wrong). Can we implement that instead?

On Sun, May 24, 2020 at 4:59 AM Alex Hall <alex.mojaki@gmail.com> wrote:
+1 on that! I can't say I have better names to suggest, but I find it remarkably difficult to figure out how to get the parts of a Path that I want path[:-1] would really hand, too, and presumably come along with any indexing. -CHB -- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython

Hello, On Sun, 24 May 2020 08:38:22 -0300 Bernardo Sulzbach <bernardo@bernardosulzbach.com> wrote:
Rather, number of components in a path. E.g. length of Path("../a") is 2.
I fully agree. For people who find string representation of paths to be nice, there's an obvious choice of not using pathlib in the first place, no confusion ensues. And people who choose to use pathlib, should not look for ways to cheat by conflating strings and Path objects with confusing implicit conversions (which will bite people who will need to work with such a code later). -- Best regards, Paul mailto:pmiscml@gmail.com

On 24.05.2020 13:27, Ram Rachum wrote:
It would be surprising to have an object which implements .__len__(), but otherwise doesn't allow any indexing, so -1 on such a change. If Paths ever get indexing, it would also be more natural to have those indexes refer to path components. The .__len__() would have to map to the number of path components, not the length of a string representation. -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, May 24 2020)
Python Projects, Coaching and Support ... https://www.egenix.com/ Python Product Development ... https://consulting.egenix.com/
::: We implement business ideas - efficiently in both time and costs ::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 https://www.egenix.com/company/contact/ https://www.malemburg.com/

On 24May2020 13:46, M.-A. Lemburg <mal@egenix.com> wrote:
It would be surprising to have an object which implements .__len__(), but otherwise doesn't allow any indexing, so -1 on such a change.
set() ? I personally don't have a fundamental problem with something having a size but no indexing.
If Paths ever get indexing, it would also be more natural to have those indexes refer to path components.
Aye, I think the same.
Yes, it would be confusing for len() to measure something different to that measured by a numeric index. I think this makes me -1 on the proposal, also because str(Path) is so easy to do. Cheers, Cameron Simpson <cs@cskk.id.au>

On 25.05.2020 03:34, Cameron Simpson wrote:
Good point. Thinking about it, my intuition wasn't fully correct: there are certainly container object types which do have a size, but don't allow direct position based access to their elements, e.g. ones which only allow iteration. Cheers, -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, May 25 2020)
Python Projects, Coaching and Support ... https://www.egenix.com/ Python Product Development ... https://consulting.egenix.com/
::: We implement business ideas - efficiently in both time and costs ::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 https://www.egenix.com/company/contact/ https://www.malemburg.com/

On 5/24/20 7:27 AM, Ram Rachum wrote:
While the length of the string representation might be one definition of length, so might the 'depth' (how many directory levels down does it go), and that might be considered more fundamental, and thus a better choice if anything was defined as its 'length' (which I am not sure it should). Length (as you are using it) is really a sequence like property, and paths are that much like a sequence. -- Richard Damon

On Sun, May 24, 2020 at 1:58 PM Ram Rachum <ram@rachum.com> wrote:
I know, that's why I said parts. But remembering or looking that up is not easy either, so being able to skip that and just do things like index directly could be useful. On the other hand, str(path) is pretty essential knowledge, there's no point in helping people not look that up.

On Sun, May 24, 2020 at 02:27:00PM +0300, Ram Rachum wrote:
It would have been even nicer if we could compose functions: sorted(paths, key=len∘str, reverse=True) *semi-wink*
It isn't obvious to me that the length of a path is the length of the string representation of that path. I thought that the length of the path would be the number of path components: /tmp/file # length 2 I wouldn't have predicted that was the length of the string representation of the path. -- Steven

Hi Steven, On Sun, May 24, 2020, 8:14 AM Steven D'Aprano
sorted(paths, key=len∘str, reverse=True) *semi-wink*
Do you have an evil twin with whom you share email. Yesterday you were arguing against functional programming style on the grounds that filtering based on a predicate was too obscure represented as 'filter(pred, ...))' Today you want full function composition. Fwiw, I like function composition. This example makes a good case for it. Too bad that symbol isn't on most keyboards. I don't think 'compose(len, str)' looks so terrible, but it's longer.

Hello, On Sun, 24 May 2020 08:23:29 -0400 David Mertz <mertz@gnosis.cx> wrote:
Could find a use for that @ operator, finally. Note to myself: should implement that for my pycopy-dev dialect (https://github.com/pfalcon/pycopy). Currently it allows to override methods on (selected) builtin types, but not operators. Need to put that on TODO. -- Best regards, Paul mailto:pmiscml@gmail.com

On Sunday, May 24, 2020, at 08:07 -0400, Steven D'Aprano wrote:
It started with y = len(str(f(g(h(x))))), which is ugly. Some people like pipes, and wrote object-like functions that could be composed with the "|" character slash: y = x | h | g | f | str | len (Or something like that. Maybe there was a ">" at the beginning.) Then a clojure fan I know showed me function called "thread": f = thread([len, str, f, g, h]) y = f(x) An untested Python implementation: def thread(fs): # or maybe you like def thread(*fs) instead fs = reversed(fs) # or not, depending on how fs was constructed def inner(x): for f in fs: x = f(x) return x return inner sorted(paths, key=thread([len, str]), reverse=True) On the one hand, it's not quite as concise as composing the functions directly. On the other hand, ∘ ruffles a lot of ASCII feathers (but I'm sure Steven knows that).

What's wrong with using @? If I understand correctly, it's used for matrix multiplication, which is far enough from function composition to avoid confusion. And it's slightly similar visually to a circle. On Sun, May 24, 2020 at 4:25 PM Dan Sommers < 2QdxY4RzWzUUiLuE@potatochowder.com> wrote:

On Sun, May 24, 2020 at 3:38 PM Ram Rachum <ram@rachum.com> wrote:
During discussions of this PEP, a similar suggestion was made to define @ as a general purpose function composition operator, and this suffers from
The matrix multiplication PEP (https://www.python.org/dev/peps/pep-0465/) says: the same problem; functools.compose isn't even useful enough to exist. That particular sentiment seems to come from here: https://mail.python.org/archives/list/python-ideas@python.org/message/JQQ6IV... More generally, the discussion is easy to search for: https://mail.python.org/archives/search?mlist=python-ideas%40python.org&q=matrix+function+composition

Changed subject line. This is far from original topic. On Sun, May 24, 2020, 9:35 AM Ram Rachum <ram@rachum.com> wrote:
I like @. Function composition is kinda-sorta enough like for product that I can even make sense of the same operator. But how would you go about getting a .__matmul__ attribute onto all functions. For ones you write yourselves, you could decorate them at definition. What about all the other functions though. I suppose this is possible: @compasable def foo(x): return x + "foo" len = composable(len) newfun = len @ foo @ str.upper nbar = newfun("bar") # 6 I deliberately didn't decorate str.upper in the example since it shouldn't matter for a left-associative operator.

On Sun, May 24, 2020 at 10:49:45AM -0400, David Mertz wrote:
As a functional programming fan, you might not know about this, but we object oriented programming people have this concept called "inheritance" where we would add the attribute to FunctionType once, and just like magic every function would support it! *wink* Steven (the evil twin) P.S. sadly, operator dunders are looked up on the class, not the instance, so adding a `__matmul__` method to individual functions doesn't help. It would have to be done at the class.

On 25/05/20 3:27 am, Ram Rachum wrote:
Speaking of evil, another evil idea would be to write code that modifies the ast and changes @ between functions to call a compose function instead.
How do you tell just from the ast whether a particular @ is between functions? -- Greg

On Sun, May 24, 2020 at 11:21 AM Steven D'Aprano <steve@pearwood.info> wrote:
I think I heard of that inheritance thing somewhere! :-) My thought is really that there's no way we're going to get .__matmul__ added (with the meaning "compose") to FunctionType. So I was thinking about what one could do with a separate module. I.e. something that could live on PyPI... before, perhaps someday, being added to standard library. Is there an evil way to monkey patch FunctionType? I.e. without actually recompiling a fork of the interpreter. Alex' forbiddenfruit.curse is pretty cool looking. But it does require explicitly cursing each individual function that might be composed. Alex Hall:
I think once you compose three or more functions, lambda starts to look pretty bad. This is only slightly mitigated by one of those proposals for "a more concise lambda" that occur intermittently. And if you actually save a composed function under a meaningful name for later use, that much more so. Of course, I'm really not all that bothered by `my_op = compose(fun1, fun2, fun3, fun4)`. The @ might be cool, but writing my own HoF compose() isn't hard. I've been playing more lately with R Tidyverse. It's pipe with currying of first argument is actually really nice. The pipe operator is god-awful ugly. But other than that it works nicely. For example: iris %>% group_by(Species) %>% summarize_if(is.numeric, mean) %>% ungroup() %>% gather(measure, value, -Species) %>% arrange(value) It's not abstract composition since it always starts with a concrete object the several operations work on. But it is some of the same feel. -- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions.

On Sun, May 24, 2020 at 6:56 PM David Mertz <mertz@gnosis.cx> wrote:
forbiddenfruit patches the type, look again. I used a loop because there isn't just one FunctionType: `type(compose) != type(len)`.
It does? How? It sounds like the problem isn't the syntax for lambda, but just function composition in general, maybe because of too many parentheses. Maybe you'd like to change: foo = bar(len(str(path))) into foo = (bar @ len @ str)(path) In any case, it's rare to be able to simply compose three unary functions, e.g. `sorted(paths, key=bar @ len @ str)`. Usually you need to do something slightly more complicated, e.g. a second argument somewhere, and if you throw in functools.partial I think that's easily worse.
Why is: my_op = bar @ len @ str even more of an improvement over: my_op = lambda p: bar(len(str(p))) than @ is an improvement in the previous examples? If the answer is that you're not supposed to assign lambdas, I think the same logic should dictate not assigning compositions. In fact, the reason to not assign lambdas is for better tracebacks, and now that I think of it, function composition would completely destroy tracebacks. For example: ``` for function in [ lambda p: str(len(p)), str @ len, ]: try: function(3) except: traceback.print_exc() ``` Output: ``` Traceback (most recent call last): File "main.py", line 15, in <module> function(3) File "main.py", line 11, in <lambda> lambda p: str(len(p)), TypeError: object of type 'int' has no len() Traceback (most recent call last): File "main.py", line 15, in <module> function(3) File "main.py", line 5, in <lambda> return lambda *args, **kwargs: self(other(*args, **kwargs)) TypeError: object of type 'int' has no len() ``` https://repl.it/@alexmojaki/LeanEnergeticPublisher-1 So based on that I'm strongly against this kind of proposal without syntactic support. I've been playing more lately with R Tidyverse. It's pipe with currying of

On Sun, May 24, 2020, 3:43 PM Guido van Rossum <guido@python.org> wrote:
I’ve never been able to remember whether (f@g)(x) means f(g(x)) or g(f(x)). That pretty much kills the idea for me.
Well, it means whichever one the designers decide it should mean. But obviously it's a thing to remember, and one that could sensibly go the other way. On the other hand, when I showed an example using filter() a couple days ago, I had to try it to remember whether the predicate or the iterable came first. Lots of such decisions are pretty arbitrary.

On Sun, May 24, 2020 at 10:53 PM David Mertz <mertz@gnosis.cx> wrote:
But when you *read* a call to filter(), it's generally pretty obvious which argument is which, even if you don't remember the signature. You just need to see which one's callable or which one's iterable (few things are both). You can probably guess just from the variable names. If you read `f@g`, you can only guess if one of `f(g(x))` or `g(f(x))` is nonsense, which I think is typically less obvious. Taking the `len@str` example, `str(len(path))` is a sensible expression, it's just a terrible sort key, and having to think that far sucks for readability. Also in the paths example, if you remember wrong and write the equivalent of `sorted(paths, key=lambda path: str(len(path)))`, you get a list in nonsense order which may be confusing to debug. If you get the order wrong in `filter()`, you should eventually get an error like `TypeError: 'list' object is not callable`.

On Sun, May 24, 2020, 5:11 PM Alex Hall
I definitely agree. My way of remembering which way filter goes was to try the wrong one in a shell and get an exception. In contrast, quite likely both f(g(h(x))) and h(g(f(x))) produce a value, but only one is the one I want. If x is string, or a list, or a number, or a NumPy array, most functions I'd call return something of the same type. Mutations are usually not order independent.

[Guido]
I’ve never been able to remember whether (f@g)(x) means f(g(x)) or g(f(x)). That pretty much kills the idea for me.
[David Mertz]
Best I know, f@g applies g first in every language that implements a composition operator, and in mathematics. While that may be arbitrary, it's easy to remember: (f@g)(x) "looks a heck of a lot more like" f(g(x)) than g(f(x)) because the former leaves the identifiers in the same order.

Dang, you're right. It works as you'd expect without overthinking it. :-) I guess what has always (seriously, since my undergrad years studying math!) confused me is that somehow when this function is introduced <https://en.wikipedia.org/wiki/Function_composition> they say (g*f)(x) = g(f(x)). The professor starts by showing f(x), and then shows how you can apply g() to the result, and lo, you have defined g*f. (I'm sorry, I refuse to learn how to type the symbol they actually use. :-) On Sun, May 24, 2020 at 2:17 PM Tim Peters <tim.peters@gmail.com> wrote:
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...>

Tiimmy:
On Sun, May 24, 2020 at 5:39 PM Guido van Rossum <guido@python.org> wrote:
Dang, you're right. It works as you'd expect without overthinking it. :-)
I think one thing that pulls in different directions is that both composition and piping are useful, and do something closely related. But in one the data "goes" forward and in the other the data "goes backward." I use bash a lot, and writing something like this is common: cat data | sort | cut -d; -f6 | grep ^foo | sort -r | uniq -c My data is moving left to right through the operations. In contrast, I might write in some hypothetical programming language: myfilter = uniq[count] ∘ sort[reverse] ∘ grep[^foo] ∘ cut[;,f6] ∘ sort result = myfilter(data) (In my just-now-invented PL, the square brackets are some kind of partial application operators) Here my data is moving right to left within the composition. Haskell spells pipes approximately `>>`, F# as `|>`, R as `%>%`, and so on. But some of the same languages also have composition (point-free style) that reverses the order of operations (using different symbols). -- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions.

On Sun, May 24, 2020 at 06:31:46PM -0400, David Mertz wrote:
The same rule applies to function application though.
I use bash a lot, and writing something like this is common:
cat data | sort | cut -d; -f6 | grep ^foo | sort -r | uniq -c
And today's "Useless Use Of cat Award" goes to... :-) sort data | ... (What is it specifically about cat that is so attractive? I almost certainly would have done exactly what you did, even knowing that sort will take a file argument.)
Compared to the regular old function call syntax: uniq(sort(grep(cut(sort(data))))) (ignoring extra arguments) where the data still moves right to left. On the third hand, even if your language doesn't have pipes, it may have methods: data.sort().cut().grep().sort().uniq() which now moves left to right again. -- Steven

On Mon, May 25, 2020 at 8:54 AM Steven D'Aprano <steve@pearwood.info> wrote:
<data sort | ... (When you use cat in a pipeline: https://http.cat/304 )
Maybe it's function call syntax that is the weird one. It might be familiar to us, but maybe it's the one that's actually causing the weirdness. Suppose we wrote the function last: (data)sort Now it's basically become a pipeline. And yet this syntax looks so utterly bizarre that none of us would accept it... ChrisA

On 24/05/2020 23:58, Chris Angelico wrote:
((path)str)len # First does something with 'path' and 'str', then does something with the result and `len'. # Not: first does something with `len' and ... what? Jesus, why is the whole world not using it already? Maybe for the same reason the world still uses QWERTY keyboards. Maybe that's what programmers (well, the smart ones:-)) in 2100 using Python 42 will use. Hell, it's currently illegal syntax. Maybe it could be introduced soon (would the new parser help? :-)) , in the hope that people will gradually fall out of the old usage `sort(data)`, so that eventually it can be deprecated and finally dropped (by 2100 or thereabouts). (Guido, are you listening?)

On Sun, May 24, 2020, 9:03 PM Rob Cliffe via Python-ideas
Forth was created in 1970. The idea isn't brand new. C is two years newer (1972). But Algol is 12 years older (1958). Most languages of the last 30 years seem to be influenced most by C, or at least the Algol tradition for basic syntax. That doesn't mean it's better, but it won out for whatever reason. Smalltalk (1972) is interestingly different from either the Forth or Algol style. But closer to Forth. Fwiw, I don't think I'd have much difficulty adjusting to the postfix calling style, but Python isn't going to do that, even in version 17.3 in 2100.

On Sun, May 24, 2020 at 6:56 PM Steven D'Aprano <steve@pearwood.info> wrote:
This is probably going afield since it is a bash thing, not a Python thing. But I can actually answer this quite specifically. When I write a pipeline like that, I usually do not do it in one pass. I write a couple of the stages, look at what I have, and then add some more stages until I get it right. Many of the commands in the pipeline can take a file argument (not just sort, also cut, also grep, also uniq... everything I used in the example). But I find fairly often that I need to add a step BEFORE what I initially thought was first processing step. And then I have to remove the filename as an argument of that no-longer-first step. Rinse and repeat. With `cat` I know it does nothing, and I won't have to change it later (well, OK, sometimes I want -n or -s). So it is a completely generic "data" object ... sort of like how I would write "fluent programming" starting with a Pandas DataFrame, for example, and calling chains of methods.. -- The dead increasingly dominate and strangle both the living and the not-yet born. Vampiric capital and undead corporate persons abuse the lives and control the thoughts of homo faber. Ideas, once born, become abortifacients against new conceptions.

JavaScript has an early proposal for this use-case: https://github.com/tc39/proposal-partial-application#pipeline-and-partial-ap... where "cat data | sort | cut -d; -f6 | grep ^foo | sort -r | uniq -c" could be represented as: data |> sort |> cut(?, delimeter=";", fields=6) |> grep(?, "^foo") |> sort(?, reverse=True) |> uniq(?, count=True) A very similar operator already exists for Hack which has served to clean up a lot of code at Facebook: https://docs.hhvm.com/hack/expressions-and-operators/pipe ( fun fact: I was one of the people who asked for __matmul__ back in the day, but now I prefer the pipeline operator as it's not restricted to unary functions and is, IMHO, more readable https://mail.python.org/archives/list/python-ideas@python.org/message/I4DXFR... ) On Sun, May 24, 2020 at 7:33 PM David Mertz <mertz@gnosis.cx> wrote:

On 24May2020 19:28, David Mertz <mertz@gnosis.cx> wrote:
People forget that redirections may appear anywhere rather than just at the end of the command: <data sort | cut | ... >output But I take your point on incremental build-the-pipeline, I do that a lot. Cheers, Cameron Simpson <cs@cskk.id.au>

Here's another whacky idea: how about adding a so-called FuncSeq to the collections library for composing functions? Since FuncSeq is (obviously) a sequence, that might help with the intuition of the function application order. The idea is the function application order is the same as the iteration order of the sequence: from collections import FuncSeq fun_seq = FuncSeq([str, len]) fun_seq(789) # 3 fun_seq[::-1]("789") # '3' --- Ricky. "I've never met a Kentucky man who wasn't either thinking about going home or actually going home." - Happy Chandler

Really, I don't think new syntax is needed for that, as it is trivially resolved by a function call (I would type an example but Ricky's answer covers it) Moreover, such a callable, or other helper-callables can trivially enable ways of connecting the piped values through specific (positional/named) parameters in the next calls - allowing a whole Graph of functions to be composed. (And it is so easy to do and flexible extendable, I can't see a place for that in the stdlib) On Mon, 25 May 2020 at 00:11, Ricky Teachey <ricky@teachey.org> wrote:

On Sun, May 24, 2020 at 11:17 PM Tim Peters <tim.peters@gmail.com> wrote:
I personally find it easy to remember, but I can imagine (perhaps incorrectly) how others may find it difficult. There's a 'wrong' rule that's equally easy to remember: f@g means call f then call g. In fact, when you think of it like that, you might not even notice that this means g(f(x)). So even if you know that f@g means f(g(x)), you can still mess this up by not thinking it through. This is similar to when you have a list of decorators. Lots of people expect them to be called top to bottom ( https://stackoverflow.com/questions/27342149/decorator-execution-order) and it's not easy to make the real order feel more intuitive.

On Sun, May 24, 2020 at 4:55 PM David Mertz <mertz@gnosis.cx> wrote:
That's easy, especially when everyone is talking about evil. ``` from forbiddenfruit import curse def compose(self, other): return lambda *args, **kwargs: self(other (*args, **kwargs)) for func in [compose, len]: curse(type(func), "__matmul__", compose) assert (len @ str)(789) == 3 ``` https://repl.it/@alexmojaki/LeanEnergeticPublisher#main.py But seriously, I don't see that much point to this idea. It's just slightly more concise while not being particularly readable or beginner friendly. ``` sorted(paths, key=len @ str) sorted(paths, key=lambda p: len(str(p))) ``` I think the problem is just that lambda isn't the nicest syntax, and we'd like something more concise, e.g. ``` sorted(paths, key=:len(str($))) ``` which would be more generally useful. But I know this kind of thing has been discussed to death.

Dan Sommers writes:
On the other hand, ∘ ruffles a lot of ASCII feathers (but I'm sure Steven knows that).
On the gripping hand, "<>" looks more or less like a circle :-), and we could finally put barry_as_FLUFL __past__ us! ;-) Here's a wacky syntax idea: to get a partially applied function of one argument, substitute the composition symbol for the argument. So if you have def foo(file, data) and def bar(x, parms) -> data, you could compose them with composed = foo(file, <>) <> bar(<>, parms) (closing over file and parms), and then apply composed(x). Not sure about readability, especially with lots of arguments, or usability.

As we already, in the interpreter, use _ for last result, *vars for unpacking a list of positional variables and ** for unpacking to a dictionary how about (and this already messes up I18N if you are using _() as an alias for T() to mark strings for translation). We also currently use the syntax A().B() to do call method A and then call method B of the result How about: @piped A(params).B(_, options).C(*_).D(**_) As a reasonably recognisable (ASCII only) syntax where the @piped decorator tells us to call each function as soon as we have any returned value(s). The other familiar option, (for those used to the bash shell), would be to use a bare - as the marker for where to put the result(s) from the previous stage. Steve Barnes -----Original Message----- From: Stephen J. Turnbull <turnbull.stephen.fw@u.tsukuba.ac.jp> Sent: 25 May 2020 02:28 To: Dan Sommers <2QdxY4RzWzUUiLuE@potatochowder.com> Cc: python-ideas@python.org Subject: [Python-ideas] Re: len(path) == len(str(path)) Dan Sommers writes:
On the other hand, ∘ ruffles a lot of ASCII feathers (but I'm sure > Steven knows that).
On the gripping hand, "<>" looks more or less like a circle :-), and we could finally put barry_as_FLUFL __past__ us! ;-) Here's a wacky syntax idea: to get a partially applied function of one argument, substitute the composition symbol for the argument. So if you have def foo(file, data) and def bar(x, parms) -> data, you could compose them with composed = foo(file, <>) <> bar(<>, parms) (closing over file and parms), and then apply composed(x). Not sure about readability, especially with lots of arguments, or usability. _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/LVBKHR... Code of Conduct: http://python.org/psf/codeofconduct/

While I'm -1 on the original proposal, I think the idea of PurePath.__len__ returning the number of components in the path may be worth some further consideration. Also, I'm not convinced that having indexing is a necessary prerequisite to pursue it further. On Sun, May 24, 2020 at 8:14 AM Steven D'Aprano <steve@pearwood.info> wrote:

Kyle Stanley wrote:
FYI PEP 428 says: In this proposal, the path classes do not derive from a builtin type. This contrasts with some other Path class proposals which were derived from str. They also do not pretend to implement the sequence protocol: if you want a path to act as a sequence, you have to lookup a dedicated attribute (the parts attribute). So while __len__() is not the whole sequence protocol, it seems like the idea was to explicitely use `len(path.parts)` instead of `len(path)` for this.
participants (23)
-
Alex Hall
-
Bernardo Sulzbach
-
Cameron Simpson
-
Chris Angelico
-
Christopher Barker
-
Dan Sommers
-
David Mertz
-
Greg Ewing
-
Guido van Rossum
-
Joao S. O. Bueno
-
Kyle Stanley
-
M.-A. Lemburg
-
Michael Mitchell
-
Paul Sokolovsky
-
Ram Rachum
-
remi.lapeyre@henki.fr
-
Richard Damon
-
Ricky Teachey
-
Rob Cliffe
-
Stephen J. Turnbull
-
Steve Barnes
-
Steven D'Aprano
-
Tim Peters