loopswhile-loopbreaksmalltalkgnu-smalltalk

GNU Smalltalk - Break from whileTrue loop without return


What is a simple and concise way to break from a whileTrue loop in GNU Smalltalk that doesn't require returning?

Here is my code. I want to break from the loop at Line 31 at the end if char_stack is empty.

https://gist.github.com/SYZYGY-DEV333/ea3f5eeb3473927c8faa294bb72a8858

Any help would be much appreciated.


Solution

  • One of the articles of the Byte magazine (1982) titled Building Control Structures in the Smalltalk-80 System by Peter Deutsch , shows how easy is to implement while-loop breaks for infrequent events that might happen inside the loop.

    To implement this we only need a new class and an extension to BlockClosure, making a total of 9 lines of code(!).

    The class: BlockWithExit, subclass of Object with two ivars exit and block and the following methods

    on: aBlock
      block := aBlock
    
    value
      exit := [^nil].
      ^block value
    
    exit
      exit value
    

    Extension

    BlockClosure>>withExit
      ^BlockWithExit new on: self
    

    and that's it!

    Example

    Find the max of a collection until its exhaustion or until nil is found (the infrequent event)

    maxBeforeNil: aCollection
      | max supplier loop |
      max := 0.
      supplier := aCollection readStream.
      loop := [
        [supplier atEnd]
          whileFalse: [
            value := supplier next.
            value isNil ifTrue: [loop exit].
            max := max max: value]] withExit.
      loop value.
      ^max
    

    Why does this work the way it does? Because a block with a non-local return exits from the method that defines the block.

    In this case this method is BlockWithExit>>value, therefore when [^nil] is evaluated from loop exit, the flow exits value and goes to its sender, right after loop value.

    The outstanding corollary of Deutsch's discovery is that the whole mechanism of Exceptions can be built using this very same trick of defining an exit block in an ivar like: exit := [^nil].