Dmitry Ponyatov
Dmitry Ponyatov

Reputation: 346

Yield Prolog adaptation for Smalltalk

How can I adapt the method described in http://yieldprolog.sourceforge.net to Pharo Smalltalk ?

Are there analogous to the yield and generator functions exist in Smalltalk ?

For start, how should I rewrite code from Tutorial1 with message passing:

class UnifyingVariable: def __init__(self): self._isBound = False def unify(self, arg): if not self._isBound: self._value = arg self._isBound = True yield False # Remove the binding. self._isBound = False elif self._value == arg: yield False

def personWithUnify(Person): for l1 in Person.unify("Chelsea"): yield False for l1 in Person.unify("Hillary"): yield False for l1 in Person.unify("Bill"): yield False

def main(): print("Names using UnifyingVariable:") Person = UnifyingVariable() for l1 in personWithUnify(Person): print(Person._value)

Is it possible use single thread realisation, avoiding multithreading causes lot of complexity ?

Upvotes: 1

Views: 321

Answers (1)

codefrau
codefrau

Reputation: 4633

Squeak Smalltalk has a Generator class with a yield: method (and if Pharo didn't remove it then it should still be there).

In Smalltalk, yield is not a keyword as in other languages, but implemented in Smalltalk itself. It uses a single thread with co-routines (manipulating the execution context).

Here's the first example from YieldProlog:

personWithReturnValue := Generator on: [ :g |
    g yield: 'Chelsea'.
    g yield: 'Hillary'.
    g yield: 'Bill' ].

Transcript cr; show: 'Names using a return value:'.
personWithReturnValue do: [ :p |
    Transcript cr; show: p ].

As long as the Generator is side-effect free, it can be used exactly as in other languages.

However, YieldProlog relies on side-effects when unifying its variables. The difference is that in Squeak the code up to the next yield: is executed immediately. That is, when creating the Generator, all the code up to the first yield: gets executed. On the first next call it executes all the code up to the second yield:, but answers the value of the first yield. And so on. This is so the Generator can detect when to stop executing (because it is a subclass of Stream and needs to support its atEnd interface).

This means that you would have to put the side-effects after the yield. E.g. for the second YieldProlog example this works:

Object subclass: #SimpleVariable
    instanceVariableNames: '_value'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'YieldProlog'.

SimpleVariable createInstVarAccessors.

personWithSimpleVariable := [ :person |
    Generator on: [ :g |
        g yield: false.
        person _value: 'Chelsea'.

        g yield: false. 
        person _value: 'Hillary'.

        g yield: false.
        person _value: 'Bill' ] ].

Transcript cr; show: 'Names using a SimpleVariable:'.
p := SimpleVariable new.
(personWithSimpleVariable value: p) do: [ :l1 |
    Transcript cr; show: p _value ].

but in general this makes it hard to implement YieldProlog properly. Now, since Generator is implemented in Smalltalk, it's relatively easy to fix this, too. See the changeset posted at http://forum.world.st/Generators-td4941886.html

With that change, the UnifyingVariable example works:

Object subclass: #UnifyingVariable
    instanceVariableNames: '_value _isBound'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'YieldProlog'.

UnifyingVariable createInstVarAccessors.

UnifyingVariable compile: 'unify: arg
    ^Generator on: [ :g |
        _isBound = true
            ifFalse: [ 
                _value := arg.
                _isBound := true.
                g yield: false.
                "Remove the binding".
                _isBound := false ]
            ifTrue: [
                _value = arg
                    ifTrue: [ g yield: false ] ] ]'.

personWithUnify := [ :person |
    Generator on: [:g |
        (person unify: 'Chelsea') do: [ :l1 |
            g yield: false ].
        (person unify: 'Hillary') do: [ :l1 |
            g yield: false ].
        (person unify: 'Bill') do: [ :l1 |
            g yield: false ] ] ].

Transcript cr; show: 'Names using a UnifyingVariable:'.
person := UnifyingVariable new.
(personWithUnify value: person) do: [ :l1 |
    Transcript cr; show: person _value ].

Transcript cr; show: 'Use unify to check a person:'.
person := UnifyingVariable new.
(person unify: 'Hillary') do: [ :l1 |
    (personWithUnify value: person) do: [ :l2 |
        Transcript cr; show: 'Hillary is a person.' ] ].
(person unify: 'Buddy') do: [ :l1 |
    (personWithUnify value: person) do: [ :l2 |
        "This won't print."
        Transcript cr; show: 'Buddy is a person.' ] ].

All this being said, I doubt that this YieldProlog implementation is as efficient as the actual Prolog for Squeak. You should have a look at that: http://www.zogotounga.net/comp/squeak/prolog.htm

Upvotes: 4

Related Questions