Is there a message-only method for defining a whileTrue message without recursion or compiler tricks?

Smalltalk has the whileTrue: -Message value, implemented through recursion (in VisualWorks) or by using the compiler embed (in Squeak / Pharo). Is there a way to define such a method without using one of them? If not, is there evidence that can be found anywhere?

+6
smalltalk
source share
4 answers

whileTrue: and whileFalse: always return zero. for example, if there is a normal recursive definition:

whileTrue: aBlock ^self value ifTrue: [self whileTrue: aBlock] 

ifTrue: will return nil if self is false, so the value should always be nil. This affected compiler optimization. The Smalltalk-80 V2 source blue book defines

 whileTrue: aBlock "Evaluate the argument, aBlock, as long as the value of the receiver is true. Ordinarily compiled in-line. But could also be done in Smalltalk as follows" ^self value ifTrue: [aBlock value. self whileTrue: aBlock] 

So just change your style to

 BlockContext>>myWhileTrue: aBlock | start | start := thisContext pc. self value ifFalse: [ ^ nil ]. aBlock value. thisContext pc: start 

or??

 BlockContext>>myWhileTrue: aBlock | start | start := thisContext pc. ^self value ifTrue: [aBlock value. thisContext pc: start] 

But, alas, both of them crash the VM after the second iteration, because thisContext pc does not respond to pc at the next iteration, but instead of having the top of the stack :)

However, the following works:

 ContextPart methods for controlling label ^{ pc. stackp } goto: aLabel "NB we *must* answer label so that the top of stack is aLabel as it is when we send label" pc := aLabel at: 1. self stackp: (aLabel at: 2). ^aLabel BlockContext>>myWhileTrue: aBlock | label | label := thisContext label. self value ifFalse: [^nil]. aBlock value. thisContext goto: label BlockClosure>>myWhileTrue: aBlock | label | label := thisContext label. ^self value ifTrue: [aBlock value. thisContext goto: label] 
+4
source share

I suggest the following solution:

 BlockContext>>myWhileTrue: aBlock | start | start := thisContext pc. self value ifFalse: [ ^ self ]. aBlock value. thisContext pc: start 

Instead of using recursion and compiler tricks, the above code uses reflection in the execution stack. Before starting the cycle, the method saves the current program counter in a temporary variable and resets it at the end to return to the beginning of the method. In some Smalltalk implementations, this approach can be slow, since some Smalltalk dialects confirm the stack only on demand, but in Pharo / Squeak this trick is quite feasible.

Please note that the above code does not respond to the result of the last block activation, as the original implementation of #whileTrue: does. It should be easy enough to fix, though.

+5
source share

You can also use an exception handler to return to the beginning, but this can be considered a change if the exception handling code used whileTrue: or another loop construct. So, basically, the question boils down to whether it is possible to implement a loop without drag and drop or recursion, and I think the answer to this question is no. So if recursion is forbidden, you are left to try to collect information about methods, such as setting the pc method or using an exception.

+1
source share

Just do:

BlockClousure -> whileTrue: aBlock

self ifTrue value: [aBlock value. thisContext restart. "reboot on pharo, reset on VW"]

+1
source share

All Articles