Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way in a message-only language to define a whileTrue message without recursion or compiler tricks?

Tags:

smalltalk

Smalltalk has the whileTrue:-Message implemented through recursion (in VisualWorks) or through compiler-inlining (in Squeak/Pharo). Is there a way to define such a method without using one of them? If not, is there a proof for that avaiable somewhere?

like image 401
Richard Durr Avatar asked Mar 23 '10 14:03

Richard Durr


2 Answers

I propose 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 on the execution stack. Before the loop starts the method stores the current program counter in a temporary variable and resets it at the end to jump back to the start of the method. In some Smalltalk implementations such an approach might be slow as some Smalltalk dialects reify the stack on demand only, but in Pharo/Squeak this trick is quite practicable.

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

like image 184
Lukas Renggli Avatar answered Nov 04 '22 14:11

Lukas Renggli


whileTrue: & whileFalse: always return nil. e.g. if there is a normal recursive definition:

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

the ifTrue: will return nil if self value is false and so the value should always be nil. That's reflected in the compiler's optimization. The original blue book Smalltalk-80 V2 definition is

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's 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 these crash the VM sometime after the second iteration because thisContext pc doesn't answer the pc on the next iteration, but instead whatever the top of stack is :)

However the following does work:

ContextPart methods for controlling
label
    ^{ pc. stackp }

goto: aLabel
    "N.B. 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]
like image 4
Eliot Miranda Avatar answered Nov 04 '22 14:11

Eliot Miranda