On Fri, 4 Mar 2022 at 02:48, Steven D'Aprano <steve@pearwood.info> wrote:
On Thu, Mar 03, 2022 at 10:30:32PM +1100, Chris Angelico wrote:
On Thu, 3 Mar 2022 at 21:14, Steven D'Aprano <steve@pearwood.info> wrote:
What did I say that made you think I denied the existence of filtered iteration? Was it the post where I pointed out we've been able to do filtered iteration going back to Python 1.x days?
ANYTHING can be done by composing concepts. We don't need anything more advanced than Brainf*. Why do we have better concepts? Because they do a better job of expressing abstract concepts.
They also do a better job of expressing *concrete* concepts, like addition.
I believe this is a BF program to read two single digit numbers, add them, and print the result:
,>,[<+>-]<------------------------------------------------.
This is a BF program that expresses an abstract concept of addition. On what basis do you consider addition to be a concrete concept? Is Python's idea of addition a single machine instruction? What, in your view, makes one thing abstract and another thing concrete? I put it to you that "add two numbers" is an abstract concept. Even more so, "add two things" is definitely an abstract concept, and we have a clean way of expressing this, regardless of whether it's string concatenation, list extension, or numeric arithmetic. The BF program you show clearly demonstrates that there are concrete actions that BF is capable of, and they can be composed into the abstract idea of integer addition. Composition can do anything! Why do we need better languages? Because they are better able to express abstract concepts like "add two integers". Calling addition "concrete" because it has a simple spelling *in Python* is the Blub Paradox at its best. You are denying features that don't exist because composition can create them, and dismissing the exact same argument about features that do exist, simply because... they do exist. Addition is no more or less abstract than iteration, or than filtered iteration, or than anything else. It's not a question of "this is abstract, this is concrete". It's that pretty much everything we ever think about is an abstract concept, to be implemented by the compiler/interpreter in its own concrete way. The real question is: Which abstract concepts deserve syntax? (In fact, even BF's fundamentals could be considered abstract concepts. But doing so would require that you be willing to consider "concrete" to be the level of electrical engineering. Or domino engineering. A half-adder can be implemented using a few square meters of racing dominoes, and once you design that abstract concept, you can build a four-bit adder with a full room of dominoes. When you think on THAT scale, even a simple 'or' gate is a very abstract concept!!)
I don't think that the difference between the status quo and the proposal:
# status quo for a filtered loop for item in items: if condition: block
# proposal for a filtered loop for item in items if condition: block
is comparable to the difference between BF and Python. How about you?
There is a difference of degree, to be sure. But in the status quo, the programmer has to put the condition into the body, since there's no way to express it in the header. A for loop can be implemented more concretely using a while loop and iter/next. We don't work that way. Why? Because it doesn't adequately express the *concept* that we're trying to get across. Programming is about expressing the ideas that we programmers have, in ways that other programmers AND a computer will be able to understand. Please can you stop blub-paradoxing yourself by assuming that what exists is completely different from what doesn't exist?
To be clear, there are lots of concepts in coding. Not all of them require their own specialised syntax. We don't have specialised syntax for a try...except block inside a loop, we use composition by putting a try...except block inside a for loop.
Composition of statements is not a bug to be fixed.
Indeed, but I'm putting the viewpoint - which a number of other people have also put - that filtered iteration DOES deserve a better way of expressing it.
Better than the three or four ways we already have? Okay.
To me, comprehensions were a significant improvement over the Python 1.x status quo, because they permitted us to write list builders as expressions, which could then be embedded directly in statements or other expressions without needing temporary variables.
Generators and iterators were significant improvements, because they allow us to do things we couldn't do (easily, or at all) before.
Context managers were another significant improvement.
Aside from saving one line and one indent level, what *significant* improvement does the proposed change give us? This is not a rhetorical question.
Once again, you keep on focusing on "one line and one indent level". It's not about that. I don't know how many more ways I can word this, but *filtered iteration is an abstract concept that is very useful to express*. Stop arguing against the strawman of the indentation level, because I have never ONCE argued that I want to save lines, I have never ONCE argued that I want to save indentation levels. Please. Drop that tired line of argument.
Nobody said that the idea of filtered looping doesn't make sense. They're only questioning whether it needs its own syntax instead of composing existing syntax.
Yes. And it keeps coming up, so I think you should probably acknowledge the fact that maybe, just maybe, this is more significant than "one newline".
I have repeatedly said that it also saves an indent level. I never bothered to mention the saving of one colon, because I thought that even for Python-Ideas that would be too trivially unimportant to mention.
And I never argued that it's worth saving the colon either, so you're still arguing against something that isn't the point.
What else does it give us? The one-line proposal and the two line status quo express exactly the same concept: a loop with a filter.
The two-line version is a loop, followed by a check, followed by skipping the rest of the loop body. The proposal is a loop over part of a list. This is not the same thing.
The functional programming idiom using filter() is even better at expressing that concept, at least for the case where the predicate is a function, but many people have an unfortunate aversion to f.p. idioms and so won't use it.
Yes, so where there predicate IS a function, great! But the versions where it's not a function are much uglier. There are several, on an ugliness spectrum, and the ones that aren't quite as ugly tend to be quite inefficient to run, so people will tend to shy away from them. I wouldn't mind that if they were really beautiful, but they aren't beautiful enough to ignore performance completely.
You're thinking FAR FAR too concretely about this. It's not about newlines.
Of course it is. The whole point of the proposal is to move a two line statement into a single line. Earlier in this thread, I pointed out that this proposal adds no new functionality to Python. It doesn't allow us to do anything we can't already do, or even make it easier to do it.
Literally all it saves is a newline and an indent.
No, it is not. It is expressing the concept of filtered iteration.
Which the existing idioms already do. So we have three or four ways of expressing filtered iteration, and the proposal is for another way of expressing filtered iteration which differs in that it saves a line and an indent level, and let's not forget that vitally important colon.
Is there any other difference that I have missed?
Filtered iteration is not "loop over everything, then inside the loop body, do a check, and possibly skip the rest of the body". Filtered iteration is "loop over some of the list".
Do you, or don't you, accept that that is a concept? One moment you say that it is a concept but you think it shouldn't get dedicated syntax,
Yes. Many concepts don't have dedicated syntax.
We don't have dedicated syntax for, say, recursion. (We just use a function call.) Or for getting the length of a sequence (another function call). Or sorting.
then the next, you imply that it isn't even a concept,
Citation required.
You're arguing that it is exactly the same as the composition of multiple statements.
and all we're doing is reformatting code. That is simply not the case.
Okay, now we're getting somewhere! So there is a semantic difference between the status quo and the new proposal. I'm sorry, I missed that! Mea culpa. Please take pity on me and explain what I have missed.
In the status quo, we have a filtered loop written as:
for item in items: if condition: block
but of course you know that already :-)
So how does the proposal differ?
for item in items if condition: block
means what, if it isn't merely a reformatting of the status quo?
Well, if "a + b" is just a reformatting of the BF code you posted, then yes, it's merely reformatting.
It's about expressing programmer concepts.
Right. And composing a for-loop with a if statement expresses that concept perfectly. As does filter().
No, it doesn't.
Wait, you are saying that
for item in filter(predicate, items)
*doesn't* express the concept of a loop with a filter?
Then what does it express?
This does. The trouble is that it ONLY expresses that cleanly in the case where the predicate is already a function. Otherwise it gets cluttered with the mess of lambda functions, and it's a lot less clear.
And what do you say to the poster who wrote about "filtered iteration":
[quote] ... you have this option:
for thing in filter(isinteresting, stuff):
which actually looks good. I think this is a pretty clear indication that the idea [filtered iteration] makes sense: functional programming languages have an idiom that aligns perfectly with it [/quote]
The author of that post certainly sounds like he thinks that the f.p. idiom `for item in filter(...)` expresses filtered iteration "perfectly".
(At least for the case where the predicate is a function. I don't think he is too fond of lambdas.)
Exactly. Try the versions where that isn't the case, and you'll see why. We have [x + 1 for x in stuff if x % 3] without lambda functions. We could have just used [x + 1 for x in filter(lambda x: x % 3, stuff)] but that's just ugly. There is a huge difference between filter() with an existing, and usefully-named, predicate, and filter() with a lambda function (or worse, a single-use global function whose entire purpose is that filtration). Lambda functions are extremely useful when they're needed. They are NOT arbitrary expressions that can be inserted anywhere. Blub Paradox has you thoroughly in its grip. ChrisA