[Python-ideas] Loop labels

Terry Reedy tjreedy at udel.edu
Fri Mar 9 19:16:24 CET 2012


On 3/9/2012 3:22 AM, Matt Joiner wrote:
> Yeah it's definitely non trivial. Reading this is what got me thinking
> about it in Python: mortoray.com/2011/10/23/the-ideal-language-has-goto/
> <http://mortoray.com/2011/10/23/the-ideal-language-has-goto/>

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




More information about the Python-ideas mailing list