Continue and Break in Ottawa

The second part of the Superpowers Workshop of the Ottawa Smalltalk user’s group caught my interest. The attendees are discussing on how to implement break and continue in Smalltalk.

Unfortunately the quality of the video was not good enough to be able to steal their example, so I had to build my own. The commented parts should trigger the well known loop operations and continue with the next iteration of the loop, respectively break the execution of the loop.

1 to: 5 do: [ :index |
index = 2
ifTrue: [ " continue " ].
index even
ifTrue: [ " break " ].
Transcript show: index; cr ]

The above example exercises the two hypothetical constructs. If you execute the code in your brain you should print out the numbers 1 and 3 on the transcript.

The first possible solution that comes to my mind are exceptions. Smalltalk has very powerful exceptions that can do all kind of magic things, but here we just need to signal and catch them at the right place. I created two new exception classes ContinueLoop and BreakLoop, then I put a handler inside the loop to continue, and a handler outside the loop to break. This gives us the first working solution. Admittedly it looks very ugly:

[ 1 to: 5 do: [ :index |
[ index = 2
ifTrue: [ ContinueLoop signal ].
index even
ifTrue: [ BreakLoop signal ].
Transcript show: index; cr ]
on: ContinueLoop
do: [ :err | ] ] ]
on: BreakLoop
do: [ :err | ]

Somebody of the Ottawa Smalltalk user’s group suggested to use continuations and they eventually come up with a pretty complicated solution. In fact, if you have the Seaside classes loaded in your image, the use of continuations provides a pretty strait-forward solution to the problem. You wrap the complete loop into a continuation to break, and the body of the loop in a continuation to continue. Evaluating the block arguments break and continue jumps right to the place after you created the respective continuations and resumes execution from there. This gives us the expected behavior:

Continuation currentDo: [ :break | 
1 to: 5 do: [ :index |
Continuation currentDo: [ :continue |
index = 2
ifTrue: [ continue value ].
index even
ifTrue: [ break value ].
Transcript show: index; cr ] ] ]

The solution is quite readable, but it basically uses a sledgehammer to crack the nut. Also continuations are pretty heavyweight to use in tight loops like this, because they essentially create a snapshot of the complete execution stack. And in this example we are not interested into the stack at all, we just want to jump back to the place where the continuation was defined. Luckily jumping back is something plain old Smalltalk blocks can do as well:

withEscaper: aBlock
aBlock value: [ ^ nil ]

This small helper methods evaluates aBlock and passes another block into it that returns nil. It is important to note that a return in Smalltalk always returns from the lexical scope, that means it returns from the method the return was defined in. In this example, evaluating the block [^ nil ] returns from the method #withEscaper:. Of course, this only works if the method, #withEscaper: is still on the execution stack (if not, we had to use continuations).

We can use the above helper method to implement the final version of our loop. This time without using magic, but simple and fast Smalltalk blocks:

self withEscaper: [ :break | 
1 to: 5 do: [ :index |
self withEscaper: [ :continue |
index = 2
ifTrue: [ continue value ].
index even
ifTrue: [ break value ].
Transcript show: index; cr ] ] ]
Posted by Lukas Renggli at 20 October 2009, 9:13 pm with tags smalltalk, continuation link

Comments

I usually implement the inner loop in a separate method, which allows me to just use a return statement (^) to break or continue. But of course, this solution defies the very purpose of a superpowers brainstorming.

However if we come to the concluding evaluation (is it for good or evil?) I’d vote for evil or at least cumbersome. Typically the effect of break and continue are best achieved through applications ‘#detect:‘ and ‘#reject:‘ or ‘#select:‘.

PS: if you define ‘#withEscaper:‘ on ‘BlockClosure‘ itself, you get the ‘#valueWithExit‘ method of Visualworks. It might be worthwhile to return a special object that understands ‘#break‘ and ‘#next‘ such that we dont need to write ‘break value‘ but can eg write ‘loop break‘ or ‘loop next‘ (which I prefer over the ambigous continue).

Posted by Adrian at 23 October 2009, 6:12 pm link

Indeed, often the standard collection protocol is a better choice to achieve the same. With large collections however you might encounter performance problems, as creating new collections and iterating over them multiple times can be expensive.

I don’t see how implementing the inner loop (body of the loop?) in a method would help you to be able to break and continue. If you implement the loop in a new method you can break, and if you implement the body in a new method you can continue, but not both at the same time without passing some blocks around. I wonder how you would implement an object that could do both?

Posted by Lukas Renggli at 24 October 2009, 4:21 pm link

1 to 5 should also print 4, i think. But it s not the main part of the article :)

Posted by Frederic at 30 October 2009, 7:49 pm link

You are right, you cannot get both at the same time (maybe I should have written: break xor continue? :)

PS, is there no lazy iterator package for Pharo?

Posted by Adrian at 8 November 2009, 11:38 pm link