[Python-ideas] Is this PEP-able? for X in ListY while conditionZ:

Andrew Barnert abarnert at yahoo.com
Sun Jun 30 08:43:53 CEST 2013


From: Nick Coghlan <ncoghlan at gmail.com>

Sent: Saturday, June 29, 2013 4:51 PM


> On 30 June 2013 01:56, Steven D'Aprano <steve at pearwood.info> wrote:
>>  In fact, I would argue the opposite, it's not *simpler* it is *more 
> complex*
>>  because it is a special case for the if keyword:
>> 
>>  break if condition  # allowed
>>  continue if condition  # maybe allowed?
>>  return 'spam' if condition  # probably disallowed
>>  pass if condition  # what's the point?
>>  raise Exception if condition  # probably disallowed
>>  x += 1 if condition  # almost certainly disallowed
> 
> People already have to learn "for/else" and "while/else". 
> Adding
> "break if" can *only* be justified on the grounds of pairing it up
> with those two existing else clauses to make appropriate if/else
> pairs, as "for/break if/else" and "while/break if/else" 
> should
> actually be easier to learn than the status quo.


I honestly don't think it would read this way to most people; the else clause would still be confusing. Especially if it continues to means the same thing even without break if, which means as soon as you "learn" the for/break if/else rule you'll learn that it's not really true.

Also, if it comes at the cost of making comprehensions harder to learn and understand, I think people use (and see) comprehensions much more often than for/else.

Again, this is equivalent to an "until" clause, but worse in three ways: it doesn't scan well, it looks ambiguous (even if it isn't to the parser), and it doesn't have a nested block form to translate to. Compare:

    [x for x in iterable until x is None]


    a = []
    for x in iterable:
        until x is None:
            a.append(x)

    [x for x in iterable break if x is None]


    a = []
    for x in iterable:
        break if x is None
        a.append(x)

> While Joshua said he wasn't proposing
> it, permitting a semi-colon in the comprehension to separate out a
> termination clause actually reads pretty well to me (we can avoid
> ambiguity in the grammar because we don't currently allow ';' 
> anywhere
> between any kind of bracket):
> 
>    [x for x in iterable; break if x is None]
>    [x for x in data if x; break if x is None]

This seems to be much better. But only because it's fixed to the end of the comprehension; I don't see how it could be extended to work with nested comprehensions.

And that brings up a larger point.

While there are many languages with break-if/while/until-type clauses in comprehensions, I can't think of any of with such clauses in Python/Haskell-style arbitrary nested comprehensions.


In most other languages, you can't nest arbitrary clauses, just loops. Then you can attach a single optional clause of each type to the end of either the whole thing (like Racket) or each loop (like Clojure). In pseudo-Python:

    [x for row in data, x in row if x until x is None]

    [x for (row in data), (x in row if x until x is None)]

The first of these is clearly weaker in that you can only attach condition clauses to the innermost loop, but usually that's not a problem—most cases where you'd need breaks in two places are probably too complicated to write as comprehensions anyway. And, when it is a problem, you can always explicitly nest and flatten:

    flatten((x for x in row if x) for row in data until row is None)

And it has the advantage over the second one that adding syntax to separate a comprehension-wide clause is much easier than adding syntax to separate per-loop clauses. (I can't think of a way to write the latter that doesn't require extra parentheses.)

So, your suggestion turns Python list comprehensions into a hybrid of two styles: Haskell-style ifs mixed with fors, Racket-style per-comprehension break-ifs (and anything else we add in the future). While that sounds bad at first, I'm not sure it really is. I think it would be very easy to explain pedagogically, and it's trivial in grammar terms:

    comprehension ::= expression comp_for [";" comp_cond]

    comp_cond ::= comp_breakif
    comp_breakif ::= "break" "if" expression_nocond

All that being said, I still think "until" is better than "break if" even with this syntax.

> One nice advantage of that notation is that:
> 
> 1. The statement after the ";" is exactly the statement that would
> appear in the expanded loop

But it still breaks the rule that the expression is nested inside the last statement, which means it still makes explaining and documenting comprehensions more difficult. Not _as_ difficult as with a non-nested-block clause that can appear anywhere, but still more difficult than without any such clauses.

> 2. It can be combined unambiguously with a filtering clause
> 3. It clearly disallows its use with nested loops in the comprehension
> 
> Cheers,
> Nick.
> 
> --
> Nick Coghlan   |  ncoghlan at gmail.com   |   Brisbane, Australia
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> http://mail.python.org/mailman/listinfo/python-ideas
> 


More information about the Python-ideas mailing list