Thoughts on PEP315

S

Stephen Horne

PEP315 (Enhanced while loop) suggests a syntax as follows...

do:
...
while condition:
...

The motives are IMO good, but I don't like this solution. It
replicates a problem with the C do loop (even though this is actually
different to the C do loop).

Imagine that you can see the following in a piece of code...

statements1

while condition :

statements2

The question is this - is this a new old-style while loop starting at
this point, or is it the 'while' part of a new-style while loop? Given
the truncated view typical of a single screenful of code, this could
be far from clear. 'statements1' could follow after an earlier 'do:',
but it could equally follow after an earlier 'if ... :' or 'while ...
:' or whatever.

This is a situation where anyone who is unaware of the new 'do:' part
(or who simply didn't think about it) could end up being confused by
the 'while ... :' line.

This is not an unusual situation when the C 'do' loop is used, simply
because that loop is quite rarely used in practice - people don't
expect it, so when people see...

statements
}
while (condition);

the tendency is to assume that either the semicolon is a mistake, or
the while line is a conventional while loop with no body (not unusual
in C code if the condition has side-effects).

There are other issues. For example...

do :
...
while c1 :
...
while c2 :
...

Is the second 'while' line the start of a new loop, or is it a
continuation of the first loop (a second exit point)? If it is a
continuation, do you really potentially need to use a 'pass' to
prepare for a second loop...

do :
...
while c1 :
...

pass # to assert that the previous loop is ended

while c2 :
...

I basically think that this is too error-prone.

When faced with a problem inventing new language syntax, IMO the first
thing to do should always be to review other languages for existing
solutions. Why re-invent the wheel? Here, I'll limit myself to
alternate-to-while-loop structures which provide more flexibility than
a basic while loop (so I'm excluding Pascals repeat-until, for
instance, which is slightly different to but no more flexible than the
while loop).


First, ANSI Basic provides a generalised loop which is a bit more
flexible - it allows both a precondition and a postcondition.

do [while condition]
...
loop [until condition]

This can be useful in some situations, but it doesn't solve the PEP315
issue.

The C for loop deserves a quick mention because it isn't just an
integer for loop. The syntax is

for (<initialisation>; <precondition>; <'increment'>)
statement

Because the <initialisation> and <increment> can be any legal C
statements, the loop is quite flexible. Combined with the comma
operator, for instance, it allows 'in-step' loops such as this array
reverser...

for (i = <first item index>, j = <list item index>; i < j; i++, j--)
{
temp = array ;
array = array [j];
array [j] = temp;
}

Once again, though, this doesn't solve the PEP315 problem.

In terms of flexible loops, Ada is basically the Daddy. The mimimalist
for is...

loop
...
end loop;

But you can add a 'while' part or a 'for' part to the start...

while condition loop
...
end loop;

for i in 1..10 loop
...
end loop;

for i in reverse 1..10 loop
...
end loop;

And in addition, you can use an 'exit when' line (in any of the cases
above) to give essentially a conditional break...

loop
...
exit when condition;
...
end loop;

This last form finally handles the PEP315 problem. But Ada goes a
little further. Suppose you have nested loops, and you want to exit
more than just the innermost one? In Ada, you can name the loops and
specify by name which one you want to exit...

outerloopname: loop
innerloopname: loop
...
exit innerloopname when condition;
...
exit outerloopname when condition;
...
end loop;
end loop;

So what might be worth using in Python? I like the idea of an 'exit
when' line, which is basically what the PEP315 'while' line gives, but
I think a different syntax should be used. I would suggest that 'exit'
in the Ada loop means essentially what 'break' means in Python now,
except for the condition. So I would suggest the following as a
possibility...

while True :
...
break if <condition> :
...

This has no new keywords, but should be both clear and effective. The
parser should expect a semicolon immediately after existing break
keywords, so the distinction should be immediately clear to the Python
parser.

A 'continue if' might be considered as well.

That is sufficient to handle PEP315 with no new keywords and without
the 'is this a new loop?' confusion. Should the need for named loops
ever be considered in Python, there is also an obvious place to put
the name between the 'break' and 'if'.


What about all those other loop ideas, though...

1. Combining a precondition with a postcondition (ANSI Basic)...

No problem - just put the 'break if' at the end of the loop. It's
not an 'until' postcondition, but who cares. If anything, having
all loop conditions with the same 'sense' is more consistent.

while True :
...
break if <condition> :

statements after loop

2. In-step looping (C for loop)...

This is just syntactic sugar for a while loop anyway...

setup1
setup2

while condition :
statement

increment1
increment2

My personal opinion is that there is some need for a more
convenient iterate-over-integers, but this kind of stuff should
normally be done as above.

3. Named loops (Ada)...

Don't know. It may be a feature without a purpose. In the two
and a half years when Ada was my main language, I don't remember
ever using it.

4. Start-of-loop keyword which doesn't require a condition (Basic,
Ada)...

This is matched by the PEP315 'do', but is not actually necessary
if you use 'break if' for the exit point - you can simply use
'while True :' to start the loop without ambiguity.

But I admit it - the reason for going through the various languages is
because I'm sick of being accused of trying to change Python into *. I
don't think anyone can reasonably accuse me of trying to change Python
into Basic, C and Ada at the same time (maybe I should find a way to
get Prolog into the mix). Though it is slightly worrying that my
preferred idea was adapted from one particular language. Still,
traditionally I'm accused of trying to change Python into C, C++,
Java, Pascal, Haskell or more recently C# so at least Ada will (if I
remember right) make a change ;-)
 
J

John Roth

Stephen Horne said:
PEP315 (Enhanced while loop) suggests a syntax as follows...

do:
...
while condition:
...

I've snipped the rest of your lengthy analysis because I think
the problem is much deeper, and is essentially insoluble given
the structure of Python.

The difficulty is that there is no clear and obvious way of
distinguishing subordinate groupings from the main grouping.

Consider the if statement:

if something:
blah blah
elif something_else:
blither
else:
bletch

If we look at this, without knowing the syntax of the
if statement, it looks like three primary statements.
There is no a priori way of knowing, from the lexical
structure of the language, that elif and else cannot
occur by themselves, but must be preceeded by
a specific statement. You need the additional layer
of the syntactic relationships.

Consider the same structure in Ruby:

if something
blah blah
elsif something_else
blither
else
bletch
end

The thing that makes all the difference is the "end"
statement which closes the if. The elsif and the else
are clauses within the if statement, and there is no
way of missing that fact even if you don't know the
exact syntax of the if structure.

If we now go back to the loop suggestion,
it becomes rather obvious what needs to be
done:

do:
bibbity
bobbity
boo
while condition # note the lack of a colon!

This is at least unambiguous as to what is
going on.

I don't, by the way, think that we need this
particular piece of syntactic sugar, regardless
of the syntax we dress it up in.

John Roth
 
S

Stephen Horne

I've snipped the rest of your lengthy analysis because I think
the problem is much deeper, and is essentially insoluble given
the structure of Python.

The difficulty is that there is no clear and obvious way of
distinguishing subordinate groupings from the main grouping.

Consider the if statement:

if something:
blah blah
elif something_else:
blither
else:
bletch

If we look at this, without knowing the syntax of the
if statement, it looks like three primary statements.
There is no a priori way of knowing, from the lexical
structure of the language, that elif and else cannot
occur by themselves, but must be preceeded by
a specific statement. You need the additional layer
of the syntactic relationships.

I disagree. You seem to saying that the *only* relevant aspect of the
lexical structure is the use of indentation and colons. I say the use
of distinctive keywords for continuation-of-same-block-structure such
as 'elif' or 'else' is easily sufficient clarification.

The PEP315 system of...

do:
...
while condition :
...

is IMO broken simply because there is no lexical indicator in that
'while' line that it is a continuation rather than a new structure. It
isn't a distinctive keyword because 'while' can obviously be the start
of a loop.

'break if <condition> :', however, is lexically clear at the point
where it is written - the 'break' on itself isn't distinctive, but the
'if' following straight after makes it unambiguous that this is a
continuation of an existing loop rather than a separate break
statement.
Consider the same structure in Ruby:

if something
blah blah
elsif something_else
blither
else
bletch
end

The thing that makes all the difference is the "end"
statement which closes the if. The elsif and the else
are clauses within the if statement, and there is no
way of missing that fact even if you don't know the
exact syntax of the if structure.

If you don't know the syntax of such fundamental things as the basic
block structures in the language, you really shouldn't be reading or
writing code IMO. Especially when these are such common concepts among
languages that you should be able to understand it even if you've
never used Python, simply from experience of virtually any other
imperitive language.
If we now go back to the loop suggestion,
it becomes rather obvious what needs to be
done:

do:
bibbity
bobbity
boo
while condition # note the lack of a colon!

Yuck!!!

This is really no different to the following, which is quoted in the
PEP as something to move away from...

while True :
...
if condition : break
...

The fundamental problem is that the exit point is not obvious because
it is 'hidden' in the detail of the loop - it is a statement within
the loop body instead of being lexically a part of the loop.

Imagine if the if statement with an else clause were written...

if condition :
statement;
statement;
statement;
else;
statement;
statement;
statement;

That is basically the situation with break at the moment - it is a
feature of the loop, but it is not lexically related to the loop. It
is IMO useful in terms of readability to make the exit point lexically
a part of the loop - to write it at the same indent level and to add
on the colon.

This is somewhat novel - I can't think of a language that does that.
Ada does have 'exit when' but it treats it as if it were any other
statement in lexical terms. Of course Ada programmers are free to put
the 'exit when' at any indentation level they like, and the approach
of matching it to the indent level of the start-of-loop line is in my
experience far from unusual.

Anyway, the thing is that just because it is unconventional doesn't
mean that it's wrong. Treating 'break' and 'continue' as just another
statement is IMO a mistake.

There is an advantage - being able to break out of the loop from a
nested structure (such as an if) but if the break is conditional
anyway, that justification mostly disappears.
This is at least unambiguous as to what is
going on.

I don't think 'break if' would be unambiguous. I think it would be
less unambiguous than your suggestion, in fact, for two reasons...

1. Your suggestion is distinct from having an inner while loop *only*
because it lacks a colon at the end of the 'while' line. That,
IMO, is a recipe for confusion.

2. Your suggestion has the 'while' line as a standard statement, and
thus at the same indentation level as any other statement. When
reading code it would be very easy to miss this loop exit point.
I don't, by the way, think that we need this
particular piece of syntactic sugar, regardless
of the syntax we dress it up in.

You may be right - but the PEP still exists, has been around since
April without being rejected, and claims to be scheduled for inclusion
in 2.4.

Syntactic sugar is basically what high level languages are about -
readability and maintainability being the key benefits (though you are
free to write machine code directly in hex - to avoid the syntactic
sugar called assembler - if you really want) - but the syntax
currently described strikes me more as syntactic vinegar. An alternate
syntax proposal seems to me more of a positive way forward. Though my
record in these matters is somewhat less that 100% (around 0% in fact)
so feel free to call me an idiot.
 
M

M-a-S

If it was up to me, I would propose

until <condition>:
<statements>

as an equivalent to:

<statements>
while not <condition>:
<statements>

and

loop:
<statements>

for the enless loop, instead of ugly 'while True'.

M-a-S

PS. And I'd introduce the switch construction of course.
 
S

Stephen Horne

If it was up to me, I would propose

until <condition>:
<statements>

as an equivalent to:

<statements>
while not <condition>:
<statements>

If your reason for using 'until' is simply to test for 'while not',
then this doesn't make sense.

I assume you intend the 'until' to be a postcondition rather than a
precondition. Writing this at the start of the loop suggests that it
is a precondition (the word 'until' does not automatically imply a
postcondition). I want the condition to be written at the location
where it is tested - for a postcondition this means the end. Which
means you still need something to mark the start of the loop, and you
end up with...

loop:
<statements>
until <condition> :

Nothing wrong with that in particular of course, but it's not so
different to the 'break if' I proposed. No different at all, in fact,
except using a different name and...
loop:
<statements>

for the enless loop, instead of ugly 'while True'.

I have no problem with that in principle, except that adding new
keywords can be a problem in itself. 'break if' avoids having a new
'until' keyword, and does not require a 'loop' keyword to replace the
'while'. Having a 'loop' keyword could be a good thing or not - it's a
different issue.

Of course, it might be acceptable to make the condition in the 'while'
optional, so you could write...

while :
<statements>
break if <condition> :


But then there would be the whole explicit-is-better-than-implicit
thing - I seriously doubt it would be accepted.

Alternatively...

for ever :
<statements>
break if <condition> :

The 'ever' wouldn't need to be a keyword - the lack of the 'in'
clearly differentiates it from existing 'for' loops so Python could
just validate the identifier name if it finds a colon where it expects
the 'in'.

You're suggestion of 'loop' certainly looks better than either of
these, but my suspicion is that looking good wouldn't outweigh the
need to avoid adding too many new keywords ("loop" may be used a lot
as an identifier name).
 
J

Jordan Krushen

for ever :
<statements>
break if <condition> :

You could always hide something similar in a generator:


def forever():
while True: yield True

for now in forever():
print 'looping forever...'


....although I must admit, I don't really mind "while True:" that much.

syntactic-sugar-is-bittersweet-ly y'rs,

J.
 
H

Heather Coppersmith

You could always hide something similar in a generator:

def forever():
while True: yield True
for now in forever():
print 'looping forever...'

...although I must admit, I don't really mind "while True:" that
much.

Don't forget that values other than "True" are true, too
(shamelessly stolen from a previous post from Tim Peters):

# basic file iteration (which can be done in other ways, but
# simply demonstrates my point)
while "there is another line in the file":
nextlinefromfile = getnextlinefromfile( )
if not nextlinefromfile:
break
moresprocessing( )

# typical server-like endless loop
while "[heck] has not frozenover":
command = getnextcommand( )
actoncommand( command )

strings-are-true'ly yours,
Heather
 
T

Tim Rowe

On Mon, 22 Sep 2003 21:47:05 -0400, "John Roth"


Yuck!!!

This is really no different to the following, which is quoted in the
PEP as something to move away from...

while True :
...
if condition : break
...

The fundamental problem is that the exit point is not obvious because
it is 'hidden' in the detail of the loop - it is a statement within
the loop body instead of being lexically a part of the loop.

It is very different indeed. In the second case stuff can go after
the "if condition: break", so the terminating condition is potentially
buried in the surrounding code. On my reading of John's proposal
nothing can go after the "while condition", so the end is clearly
marked in the Python way by the indentation dropping back a level.

What I /don't/ like about it is the overloading of the "while"
keyword. I would much sooner reverse the condition and use "until",
though I realise the back-compatibility issues of introducing new
keywords.
 
J

John Roth

Tim Rowe said:
It is very different indeed. In the second case stuff can go after
the "if condition: break", so the terminating condition is potentially
buried in the surrounding code. On my reading of John's proposal
nothing can go after the "while condition", so the end is clearly
marked in the Python way by the indentation dropping back a level.

Thanks, Terry. That's exactly what I meant to convey.
What I /don't/ like about it is the overloading of the "while"
keyword. I would much sooner reverse the condition and use "until",
though I realise the back-compatibility issues of introducing new
keywords.

That's always a problem, although my understanding of the
objectives for 2.4 is that there will probably be a number of
new keywords, so one more won't make a significant
difference. 2.3 was supposed to avoid adding them.

My major objection is that I don't think there's enough usage
to justify the additional complexity, regardless of whether we
can come up with a clean, Pythonic way of expressing it.

John Roth
 
S

Stephen Horne

It is very different indeed. In the second case stuff can go after
the "if condition: break", so the terminating condition is potentially
buried in the surrounding code. On my reading of John's proposal
nothing can go after the "while condition", so the end is clearly
marked in the Python way by the indentation dropping back a level.

Maybe - but in my reading of Johns suggestion the 'while' was just a
statement like the existing 'break' or 'continue' (hence the lack of a
colon). So you could legally write...

Which hides the 'while' quite well He said there was no colon - he
said nothing about semicolons.

This was not true for the original PEP315 suggestion, nor for my own,
which is...

while True :
blah; blah; blah; blah
blah; blah; blah; blah
break if condition :
blah; blah; blah; blah
blah; blah; blah; blah

Even with a severe case of not-enough-whitespace, it is still clear
where the loop exit point is.
What I /don't/ like about it is the overloading of the "while"
keyword. I would much sooner reverse the condition and use "until",
though I realise the back-compatibility issues of introducing new
keywords.

I agree. That's why I suggested 'break if' - it reuses existing
keywords, yet is unambiguous and explicit about what it is doing. The
sense of the condition seems unimportant to me (a boolean not is
simple enough) so the fact that 'break if' reads more like 'until'
than 'while' seems irrelevant to me.

Actually, I kind of like the fact that it will normally be an easy
drop-in replacement for 'if condition : break'.

In itself, I don't think my suggestion is compelling - but as an
alternative to the existing PEP315, on the assumption that some change
is going to happen, I think my idea has significant merit.
 
S

Stephen Horne

Don't forget that values other than "True" are true, too
(shamelessly stolen from a previous post from Tim Peters):

# basic file iteration (which can be done in other ways, but
# simply demonstrates my point)
while "there is another line in the file":
nextlinefromfile = getnextlinefromfile( )
if not nextlinefromfile:
break
moresprocessing( )

Thanks - to you and Tim - score 1 for self-documenting code!
 
T

Tim Rowe

Maybe - but in my reading of Johns suggestion the 'while' was just a
statement like the existing 'break' or 'continue' (hence the lack of a
colon). So you could legally write...

He's clarified that one now.
while True :
blah; blah; blah; blah
blah; blah; blah; blah
break if condition :
blah; blah; blah; blah
blah; blah; blah; blah

That makes me twitchy: looks like the thin end of a wedge to Perl and
Ruby's said:
In itself, I don't think my suggestion is compelling - but as an
alternative to the existing PEP315, on the assumption that some change
is going to happen, I think my idea has significant merit.

Oh, better than PEP315 certainly. I still don't like it :)
 
S

Stephen Horne

That makes me twitchy: looks like the thin end of a wedge to Perl and
Ruby's <statement> if <condition> construct.

I don't know Perl that well, and I certainly don't know Ruby. I
certainly wasn't basing this on either of them.

Anyway, you certainly couldn't turn the form above into a <statement>
if <condition> construct.

It is a part of an existing loop construct - *NOT* a statement and
therefore *NOT* a conditional statement. The indentation and the ":"
make it clear that it isn't a statement in its own right. If anything,
it removes the possibility of a future construct like that - consider
the following...


while True :
while True :
inner_loop_stuff ()
break if ... :
inner_loop_stuff ()

break if ...
# inner loop ended by 'break if ...' indentation

outer_loop_stuff ()

If the <statement> if <condition> construct became a reality, both of
those "break if" lines would be legal - but with very different
meanings. The first is a part of the inner loop, and that inner loops
body continues afterwards. The second would be a part of the outer
loop, and its outdent would mark the end the inner loop.

This would be a nightmare for the parser - until it finds the colon or
end-of-line (or semicolon perhaps) it simply wouldn't know which case
it was dealing with.

But if this is bad for the parser, just think what it would do to
users! - forgetting a trailing semicolon shouldn't result in a
statement which is legal yet with a significantly different meaning.
In fact the semicolon on all these things at the moment is IIRC
totally redundant in terms of resolving ambiguity - it is compulsory
for stylistic reasons.

In my mind, "break if" is not the start of a general conditional
statement idea - it is a two-keyword combination in order to avoid
creating a new single keyword such as "until". It may suggest a
"continue if" for reasons of symmetry, but "continue" is IMO useless
anyway so probably not.
 
G

Gordon Airport

The point of do-while loops is that you don't need setup code anymore,
right? How about this:

....
dowhile <condition>:
<loop body>
....

where the body is run once before it becomes a standard while loop. It
looks like we all think of this structure as a "do-while loop", so the
expression is natural. This also makes it easy to change loop types -
just add or remove the "do". Slight downside is that the execution flow
isn't exactly as you read it.
 
S

Stephen Horne

The point of do-while loops is that you don't need setup code anymore,
right? How about this:

...
dowhile <condition>:
<loop body>
...

where the body is run once before it becomes a standard while loop. It
looks like we all think of this structure as a "do-while loop", so the
expression is natural. This also makes it easy to change loop types -
just add or remove the "do". Slight downside is that the execution flow
isn't exactly as you read it.

This doesn't really solve the problem that the PEP aims to solve.

The setup code in the following example (from the PEP) is only *part*
of the code in the loop...

do:
<setup code>
while <condition> :
<loop body>

The condition is tested in the middle.

The precondition form duplicates the setup - this is what the PEP was
trying to avoid...

<setup code>
while <condition> :
<loop body>
<setup code>

But the postcondition-compliant form is really no better...

do :
<setup code>
if <condition> :
<loop body>
until !<condition> :

(or if you insist)

dowhile <condition> :
<setup code>
if <condition> :
<loop body>

You're simply duplicating the condition instead of the setup code. As
loop conditions are often at least as complex as the setup code, there
is really no gain here.

In addition, the 'if' isn't explicitly a loop exit point - it's just a
nested structure that achieves the same effect - so it doesn't express
as clearly the intention behind the code.

The whole point of the syntax in the PEP is to have the loop condition
tested in the middle of the loop - something which in C (and current
Python) is handled badly by the unstructured 'break'.


Secondly, what the PEP suggests is not what most people think of as a
'do-while' loop. Anyone sufficiently familiar with C, C++, Java, ...
will see a 'do-while' loop as being postconditioned (much like
repeat-until in Pascal etc) - ie not having the loop condition tested
in the middle as the PEP proposes.


Finally, IMO execution order should match reading order unless there
is a *VERY* good reason to do differently.

Pascal, C and even Basic manage to show a precondition at the start of
a loop, but a postcondition at the end of a loop. I don't see any good
reason why Python can't follow suit.


IMO, a loop exit point should... (highest priority first)

1. Be written at the point where the condition is tested, such that
execution order is the same as reading order.

I don't want to see variables referenced in a condition that
haven't even been defined yet, for instance. You see this as
minor - I see it as *very* important.

2. To be clearly identifiable be its leading keyword(s), and in
particular not be confusable with the start of a new structure.

This is a problem with the C 'do ... while' - it isn't always
clear that the 'while' is part of the 'do' loop rather than the
start of a new 'while' loop. IMO we don't need this hassle in
Python.

3. Be linked to the loop itself, much as 'else' is linked to 'if',
by having the same indent level and the ':' marker at the end of
the line.

This is IMO relatively minor - I don't object that much to the
existing 'if condition : break' - but I believe it is important
to the PEP authors logic.


Basically, readability and maintainability are key obsessions of mine
(and pretty much anyone who has had to read and maintain large systems
written by other people).

I'm not really sure why people don't like 'break if' - but then of
course I don't, it is my invention ;-)

Maybe it's to do with the return to the indent level of the earlier
'while' - but then 'elif' and 'else' already do this in 'if' blocks.

More likely it is that bit in point 3 above - I think it is a good
idea for a loop exit point, but it isn't exactly normal practice. Adas
'exit when' is, as I mentioned, the closest match I know of.

Maybe 'break if' would end up being like a certain common reaction to
Pythons use of indentation for structuring (initial shock and disgust,
but growing to love it) or maybe I'm just wierd ;-)
 
S

Stephen Horne

The whole point of the syntax in the PEP is to have the loop condition
tested in the middle of the loop - something which in C (and current
Python) is handled badly by the unstructured 'break'.

Ooops - that 'badly' was meant to be in quotes as I don't really think
it's that bad.
 
B

Bernhard Herzog

Stephen Horne said:
The PEP315 system of...

do:
...
while condition :
...

is IMO broken simply because there is no lexical indicator in that
'while' line that it is a continuation rather than a new structure. It
isn't a distinctive keyword because 'while' can obviously be the start
of a loop.

I think someone once suggested "and while" instead of a plain while:

do:
...
and while condition:
...

This reads quite nicely IMO.

Bernhard
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top