
Several languages allow break and continue statements to take a count or loop label for greater flexibility. Dealing with nested loops and control flow around them is definitely something I and probably most programmers deal with everyday. Generally for very complex control flow one might employ functions, and use return statements to work around any shortcomings. This is not always ideal in CPython because of the performance cost of function calls, and lack of anonymous functions. The other work around is usually goto statements, which clearly aren't available or appropriate in Python. So what about the following extensions? Allow for and while statements to be labelled using "as". Allow break and continue to take the name of a containing loop, or an integer. The corresponding named loop, or the nth containing loop are treated as though they are the nearest enclosing loop. loop_label ::= identifier break_stmt ::= "break" [decimalinteger | loop_label] continue_stmt ::= "continue" [decimalinteger | loop_label] while_stmt ::= "while" expression ["as" loop_label] ":" suite ["else" ":" suite] for_stmt ::= "for" target_list "in" expression_list ["as" loop_label] ":" suite ["else" ":" suite] Here's a really naive example: for a in b as c: for d in e: break c # or break 2 for f in g: continue c # or continue 2

Le 08/03/2012 19:52, Matt Joiner a écrit :
Allow for and while statements to be labelled using "as".
I like the idea. But regardless of whether it should be added at all, I see one issue with the proposition in its current form. The with statement has a variable name after its own "as" keyword. Do loop labels live in the same namespace as variables? Or can I have a variable with the same name as a loop label without the two interfering? Regards, -- Simon Sapin

On Mar 9, 2012 3:04 AM, "Simon Sapin" <simon.sapin@kozea.fr> wrote:
see one issue with the proposition in its current form.
The with statement has a variable name after its own "as" keyword. Do
loop labels live in the same namespace as variables? Or can I have a variable with the same name as a loop label without the two interfering? The with statement is not a loop statement and would not be extended, so this is not an issue. It would be nonsensical to assign to loop labels, so I figure there would be some non trivial checks done when a function is compiled. You raise an interesting point.
I neglected to mention that nested looping often requires a lot of undesirable temporaries and additional state checks. Avoiding this a very common goal, that can be circumvented with the aforementioned constructs.

On Thu, Mar 8, 2012 at 9:14 PM, Matt Joiner <anacrolix@gmail.com> wrote:
On Mar 9, 2012 3:04 AM, "Simon Sapin" <simon.sapin@kozea.fr> wrote:
Le 08/03/2012 19:52, Matt Joiner a écrit :
... Guido has already pronounced "Labeled break and continue" rejected in PEP 3136. http://www.python.org/dev/peps/pep-3136/ tl;dr - Most use cases are better off refactored into functions and the other use cases aren't worth how much this complicates the language. Yuval Greenfield

Le 08/03/2012 20:14, Matt Joiner a écrit :
The with statement is not a loop statement and would not be extended, so this is not an issue.
Yes, no conflict here. Only the precedent of the with statement set the expectation that "as" is followed by a variable name.
So: same namespace, but incompatible usage? Ie, if a name is a loop label, any operation on it other than break/continue is forbidden? -- Simon Sapin

The biggest problem is not the new syntax. It's the new type of object that would be needed, Label, which lies in some magical place half way between a name and a keyword. What would be the result of the following code? loops = [] for i in range(4) as label: print(type(label), dir(label)) loops.append(label) for label in loops as newlabel: break label David On Mar 9, 2012 1:08 AM, "Westley Martínez" <anikom15@gmail.com> wrote:

On 3/9/2012 3:22 AM, Matt Joiner wrote:
I disagree as most would understand the claim. Let us consider his first example: location_t where; for( int i=0; i < num_rows; ++i ) { for( int j=0; j < num_cols; ++j ) { if( matrix(i,j).is_what_we_want() ) { where.set(i,j); goto found; } } } throw error( "not-found" ); found: //do something with it Let us leave aside the fact that searching the matrix should normally be factored out as a function or method unto itself, separate from code that uses the found object. A Python solution is to use the implied goto of try/except/else and make 'where' an exception: class where(Exception): def __init__(self, i, j): self.i = i self.j = j try: for i in range(num_rows): for j in range(num_cols): if matrix(i,j).is_what_we_want(): raise where(i,j) raise ValueError('matrix does not have what we want') except where as w: do_something(w) If one wants to reuse the app specific exception, replace 'raise ValueError' with the following at the end. else: raise where(None,None) Either way, this is as least as clean and clear as the goto example. One advantage is that the jump information object is created and initialized only when and where needed. A virtue of try: is that it tells the reader that there is going to be some jumpy control-flow. People too often think that exceptions are errors or only for errors. There are not (which is why those that *are* are named SomethingError). They are jump objects that carry information with the jump. Also exception gotos obey the restriction of only going up the stack. So Python already has a version of what the Mortoray advocates. (And hence, in a way, I agree with the claim ;-). The redo example can be done similarly. There are two types of jumps. class Redo(Exception): "Restart the loop process from the top" class Break(Exception): "We are completely done with the loop process" while True: try: <nested loops> if must_redo_from_top: raise Redo() <more code> if completely_done: raise Break except Redo: pass except Break: break The error-handling example is easily done with a Broken exception. If performance is an issue for a state machine, it is best written in C. If the machine is very much more complex than the example, it is best generated programmatically from higher-level specs. Code in the example is the sort of spaghetti code that become unreadable and unmaintainable with very many more states and input conditions. (I am old enough to have tried -- and sometimes given up -- working with such code.) Even in C, it is often better, when possible, to use tables to map state and input to action, output, and next state. That, of course, can be done just as well (though slower) in Python. In fact, it may be easier in Python if the table is sparse. The result can be structurally simple code like <populate table or tables> current_state = initial_state for item in input_stream: action, output, current_state = table[current_state][item] # or table[current_state].get(item,default) for sparse rows # replace 'item' with 'group(item)' to classify inputs perform(action) print(output) if current_state == exit_state: return -- Terry Jan Reedy

On Sat, Mar 10, 2012 at 4:16 AM, Terry Reedy <tjreedy@udel.edu> wrote:
No, let's *not* leave that out, because it gets to the heart of the "attractive nuisance" aspect of labelled loop proposals. The way this code should be written in Python: # You should have a class Matrix. Python is not C. # That class should offer a method to iterate over all the points in the matrix # Or, if it doesn't, you just write your own helper iterator def iter_points(matrix): for i in range(matrix.num_rows): for j in range(matrix.num_columns): yield i, j # And now the problem devolves to a conventional Pythonic search loop for i, j in iter_points(matrix): item = matrix[i, j] if item.is_interesting(): break else: raise RuntimeError("Nothing interesting found in {!r}".format(matrix)) # We use our interesting item here Note that continue in the loop body also always applies to the outermost loop because, from the compiler's point of view, there is only *one* loop. The fact that there is a second loop internally is hidden by the custom iterator. So, whenever I hear "we should have labelled loops" I hear "I forgot I could simply write a generator that produces the nested loop variables as a tuple and hides any extra loops needed to create them from the compiler". As Terry said: """Code in the example is the sort of spaghetti code that become unreadable and unmaintainable with very many more states and input conditions.""" Python offers higher level constructs for a reason. Changing the language definition because people choose not to use them would be foolish. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Fri, Mar 09, 2012 at 07:06:47AM +0200, David Townshend wrote:
Labels are neither names nor keywords nor objects. They would be instructions to the compiler, nothing more. The idea of labelled break/continue is not a new invention. Before criticising it (or for that matter, praising it), we should see how it works in other languages. Java has labelled loops: http://www.cs.umd.edu/~clin/MoreJava/ControlFlow/break.html So does Javascript: http://www.tutorialspoint.com/javascript/javascript_loop_control.htm And Groovy: http://docs.codehaus.org/display/GROOVY/JN2535-Control
NameError, because there is no name "label".
loops.append(label)
Again, NameError.
for label in loops as newlabel: break label
SyntaxError, because the "label" loop is not enclosing the break. For what it's worth, I used to be against this idea as adding unnecessary complexity, until a year or so ago when I came across a use-case which was naturally written as nested loops with labelled breaks. Now I'm sold on the idea. I ended up re-writing the code to use functions, but it really wasn't natural. It felt forced. I just wish I could remember what the algorithm was... :( -- Steven

Labels are neither names nor keywords nor objects. They would be instructions to the compiler, nothing more.
Yep
It's also in C (in the form of goto), some shells.
It's something that comes up enough to be of use. I'd compare it to the with statement, which isn't always useful, but handles the majority of such cases. A goto is the full fledged try/finally equivalent. Just as the with statement prevents people from incorrectly implementing their try/finally's for common cases, Labelled break/continues would prevent people from using state variables erroneously, and taking the corresponding performance hit either there, or by creating additional functions.

On Fri, Mar 9, 2012 at 9:08 PM, Matt Joiner <anacrolix@gmail.com> wrote:
You are setting your bar for new syntax proposals *way* too low. In a language with an iterator protocol, many nested loops are better written as generators with an embedded return. Labelled loops then become an attractive nuisance that distracts people from other ways to structure their algorithm such that it is easier to maintain. So no, you can't just wave your hands in the air and say "It's something that comes up enough to be of use" and get away with it. PEP 380 only got away with being a bit sketchy on concrete use cases because generators that were used with send() and throw() so clearly couldn't be refactored properly in a post-PEP 342 world. PEP 343 met the much higher bar of making it easy to deal with a wide range of *extraordinarily* common tasks like closing files, using thread synchronisation primitives, standardising exception handling, etc, etc. Loop labels have never even come *close* to attaining that standard. Regards, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Le 08/03/2012 19:52, Matt Joiner a écrit :
Allow for and while statements to be labelled using "as".
I like the idea. But regardless of whether it should be added at all, I see one issue with the proposition in its current form. The with statement has a variable name after its own "as" keyword. Do loop labels live in the same namespace as variables? Or can I have a variable with the same name as a loop label without the two interfering? Regards, -- Simon Sapin

On Mar 9, 2012 3:04 AM, "Simon Sapin" <simon.sapin@kozea.fr> wrote:
see one issue with the proposition in its current form.
The with statement has a variable name after its own "as" keyword. Do
loop labels live in the same namespace as variables? Or can I have a variable with the same name as a loop label without the two interfering? The with statement is not a loop statement and would not be extended, so this is not an issue. It would be nonsensical to assign to loop labels, so I figure there would be some non trivial checks done when a function is compiled. You raise an interesting point.
I neglected to mention that nested looping often requires a lot of undesirable temporaries and additional state checks. Avoiding this a very common goal, that can be circumvented with the aforementioned constructs.

On Thu, Mar 8, 2012 at 9:14 PM, Matt Joiner <anacrolix@gmail.com> wrote:
On Mar 9, 2012 3:04 AM, "Simon Sapin" <simon.sapin@kozea.fr> wrote:
Le 08/03/2012 19:52, Matt Joiner a écrit :
... Guido has already pronounced "Labeled break and continue" rejected in PEP 3136. http://www.python.org/dev/peps/pep-3136/ tl;dr - Most use cases are better off refactored into functions and the other use cases aren't worth how much this complicates the language. Yuval Greenfield

Le 08/03/2012 20:14, Matt Joiner a écrit :
The with statement is not a loop statement and would not be extended, so this is not an issue.
Yes, no conflict here. Only the precedent of the with statement set the expectation that "as" is followed by a variable name.
So: same namespace, but incompatible usage? Ie, if a name is a loop label, any operation on it other than break/continue is forbidden? -- Simon Sapin

The biggest problem is not the new syntax. It's the new type of object that would be needed, Label, which lies in some magical place half way between a name and a keyword. What would be the result of the following code? loops = [] for i in range(4) as label: print(type(label), dir(label)) loops.append(label) for label in loops as newlabel: break label David On Mar 9, 2012 1:08 AM, "Westley Martínez" <anikom15@gmail.com> wrote:

On 3/9/2012 3:22 AM, Matt Joiner wrote:
I disagree as most would understand the claim. Let us consider his first example: location_t where; for( int i=0; i < num_rows; ++i ) { for( int j=0; j < num_cols; ++j ) { if( matrix(i,j).is_what_we_want() ) { where.set(i,j); goto found; } } } throw error( "not-found" ); found: //do something with it Let us leave aside the fact that searching the matrix should normally be factored out as a function or method unto itself, separate from code that uses the found object. A Python solution is to use the implied goto of try/except/else and make 'where' an exception: class where(Exception): def __init__(self, i, j): self.i = i self.j = j try: for i in range(num_rows): for j in range(num_cols): if matrix(i,j).is_what_we_want(): raise where(i,j) raise ValueError('matrix does not have what we want') except where as w: do_something(w) If one wants to reuse the app specific exception, replace 'raise ValueError' with the following at the end. else: raise where(None,None) Either way, this is as least as clean and clear as the goto example. One advantage is that the jump information object is created and initialized only when and where needed. A virtue of try: is that it tells the reader that there is going to be some jumpy control-flow. People too often think that exceptions are errors or only for errors. There are not (which is why those that *are* are named SomethingError). They are jump objects that carry information with the jump. Also exception gotos obey the restriction of only going up the stack. So Python already has a version of what the Mortoray advocates. (And hence, in a way, I agree with the claim ;-). The redo example can be done similarly. There are two types of jumps. class Redo(Exception): "Restart the loop process from the top" class Break(Exception): "We are completely done with the loop process" while True: try: <nested loops> if must_redo_from_top: raise Redo() <more code> if completely_done: raise Break except Redo: pass except Break: break The error-handling example is easily done with a Broken exception. If performance is an issue for a state machine, it is best written in C. If the machine is very much more complex than the example, it is best generated programmatically from higher-level specs. Code in the example is the sort of spaghetti code that become unreadable and unmaintainable with very many more states and input conditions. (I am old enough to have tried -- and sometimes given up -- working with such code.) Even in C, it is often better, when possible, to use tables to map state and input to action, output, and next state. That, of course, can be done just as well (though slower) in Python. In fact, it may be easier in Python if the table is sparse. The result can be structurally simple code like <populate table or tables> current_state = initial_state for item in input_stream: action, output, current_state = table[current_state][item] # or table[current_state].get(item,default) for sparse rows # replace 'item' with 'group(item)' to classify inputs perform(action) print(output) if current_state == exit_state: return -- Terry Jan Reedy

On Sat, Mar 10, 2012 at 4:16 AM, Terry Reedy <tjreedy@udel.edu> wrote:
No, let's *not* leave that out, because it gets to the heart of the "attractive nuisance" aspect of labelled loop proposals. The way this code should be written in Python: # You should have a class Matrix. Python is not C. # That class should offer a method to iterate over all the points in the matrix # Or, if it doesn't, you just write your own helper iterator def iter_points(matrix): for i in range(matrix.num_rows): for j in range(matrix.num_columns): yield i, j # And now the problem devolves to a conventional Pythonic search loop for i, j in iter_points(matrix): item = matrix[i, j] if item.is_interesting(): break else: raise RuntimeError("Nothing interesting found in {!r}".format(matrix)) # We use our interesting item here Note that continue in the loop body also always applies to the outermost loop because, from the compiler's point of view, there is only *one* loop. The fact that there is a second loop internally is hidden by the custom iterator. So, whenever I hear "we should have labelled loops" I hear "I forgot I could simply write a generator that produces the nested loop variables as a tuple and hides any extra loops needed to create them from the compiler". As Terry said: """Code in the example is the sort of spaghetti code that become unreadable and unmaintainable with very many more states and input conditions.""" Python offers higher level constructs for a reason. Changing the language definition because people choose not to use them would be foolish. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Fri, Mar 09, 2012 at 07:06:47AM +0200, David Townshend wrote:
Labels are neither names nor keywords nor objects. They would be instructions to the compiler, nothing more. The idea of labelled break/continue is not a new invention. Before criticising it (or for that matter, praising it), we should see how it works in other languages. Java has labelled loops: http://www.cs.umd.edu/~clin/MoreJava/ControlFlow/break.html So does Javascript: http://www.tutorialspoint.com/javascript/javascript_loop_control.htm And Groovy: http://docs.codehaus.org/display/GROOVY/JN2535-Control
NameError, because there is no name "label".
loops.append(label)
Again, NameError.
for label in loops as newlabel: break label
SyntaxError, because the "label" loop is not enclosing the break. For what it's worth, I used to be against this idea as adding unnecessary complexity, until a year or so ago when I came across a use-case which was naturally written as nested loops with labelled breaks. Now I'm sold on the idea. I ended up re-writing the code to use functions, but it really wasn't natural. It felt forced. I just wish I could remember what the algorithm was... :( -- Steven

Labels are neither names nor keywords nor objects. They would be instructions to the compiler, nothing more.
Yep
It's also in C (in the form of goto), some shells.
It's something that comes up enough to be of use. I'd compare it to the with statement, which isn't always useful, but handles the majority of such cases. A goto is the full fledged try/finally equivalent. Just as the with statement prevents people from incorrectly implementing their try/finally's for common cases, Labelled break/continues would prevent people from using state variables erroneously, and taking the corresponding performance hit either there, or by creating additional functions.

On Fri, Mar 9, 2012 at 9:08 PM, Matt Joiner <anacrolix@gmail.com> wrote:
You are setting your bar for new syntax proposals *way* too low. In a language with an iterator protocol, many nested loops are better written as generators with an embedded return. Labelled loops then become an attractive nuisance that distracts people from other ways to structure their algorithm such that it is easier to maintain. So no, you can't just wave your hands in the air and say "It's something that comes up enough to be of use" and get away with it. PEP 380 only got away with being a bit sketchy on concrete use cases because generators that were used with send() and throw() so clearly couldn't be refactored properly in a post-PEP 342 world. PEP 343 met the much higher bar of making it easy to deal with a wide range of *extraordinarily* common tasks like closing files, using thread synchronisation primitives, standardising exception handling, etc, etc. Loop labels have never even come *close* to attaining that standard. Regards, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
participants (8)
-
David Townshend
-
Matt Joiner
-
Nick Coghlan
-
Simon Sapin
-
Steven D'Aprano
-
Terry Reedy
-
Westley Martínez
-
Yuval Greenfield