New version of Ghost is out. All code is now hosted in github repo https://github.com/dionisiydk/Ghost (all packages moved here). You can load it by:
Metacello new
baseline: 'Ghost';
repository: 'github://dionisiydk/Ghost';
load
Ghost is framework to implement special kind of objects which process messages in unnatural Smalltalk way.
Let's implement little example from the idea of Stephan Ducasse http://lists.pharo.org/pipermail/pharo-dev_lists.pharo.org/2016-January/118417.html.
We want to know what messages are sent to object by inspector. Inspector is implemented by composition of multiple views which makes it difficult to collect full information statically. But we can do this dynamically by sending special object to inspector which will collect all message sends. Ghost provides suitable infrastructure to implement such kind of objects.
Any ghost implementation should answer three questions:
GHGhostBehaviour subclass: #Learning
instanceVariableNames: 'studiedMessages'
classVariableNames: ''
package: 'Ghost-Learning'
Learning>>initialize
super initialize
studiedMessages := Dictionary new.
We add instance variable studiedMessages to store messages and their implementation methods from Object class. When learning will be completed we can inspect this dictionary to analyze required messages.
To process message send our behaviour should retrieve corresponding method from Object class and execute it on ghost instance:
Learning>>send: aMessage to: aGhost
| learnedMethod |
learnedMethod := studiedMessages at: aMessage selector ifAbsent: [ nil].
learnedMethod ifNil: [
learnedMethod := Object lookupSelector: aMessage selector.
studiedMessages at: aMessage selector put: learnedMethod].
"here we should explicitly execute method by primitive to not introduce new messages to ghost because it not what it learn"
^GHMetaMessages executeWith: aGhost andArguments: aMessage arguments method: learnedMethod
Any ghost behaviour should implement message processing method #send:to:.
GHMetaMessages class provides suitable set of methods to execute mirror primitives on given object without sending extra messages to them.
Now to complete our behaviour we need to implement method #currentMetaLevel which defines set of messages which should not be processed by ghost logic. It is answer to second question of any ghost implementation. Here we want to learn all messages which will sent to our ghost:
Learning>>currentMetaLevel
^GHMetaLevel empty
We can also use "GHMetaLevel standard" which will not intercept standard messages from tools. For example #printString or #class will be processed by the meta level instead of ghost behaviour logic. And #class will return class of ghost as any other object. Standard meta level is useful to debug new ghost implementation because tools will look at ghost as any other normal object. And they will not produce recursive ghost behaviour.
But in our case we want to see what exactly inspector doing with ghost. And for this we will use empty meta level.
Last thing which we need to implement is new kind of ghost itself. Let's call it Student:
GHObjectGhost subclass: #Student
instanceVariableNames: 'learning'
classVariableNames: ''
package: 'Ghost-Learning'
Any ghost should implement #ghostBehaviour method and instantiation class side method:
Student>>ghostBehaviour
^learning ifNil: [learning := Learning new]
Student class>>new
^self basicNew
And now we can execute script in workspace:
student := Student new.
student inspect.
And then inspect "student ghostBehaviour studiedMessages":
Improved version of this example is in package Ghost-Learning.
Update: Project was moved to github
Metacello new
baseline: 'Ghost';
repository: 'github://dionisiydk/Ghost';
load
Ghost is framework to implement special kind of objects which process messages in unnatural Smalltalk way.
Let's implement little example from the idea of Stephan Ducasse http://lists.pharo.org/pipermail/pharo-dev_lists.pharo.org/2016-January/118417.html.
We want to know what messages are sent to object by inspector. Inspector is implemented by composition of multiple views which makes it difficult to collect full information statically. But we can do this dynamically by sending special object to inspector which will collect all message sends. Ghost provides suitable infrastructure to implement such kind of objects.
Any ghost implementation should answer three questions:
- How ghost will behave? (what to do with any received message?)
- What messages should not be part of ghost logic and processed in natural Smalltalk way?
- How to instantiate ghosts?
GHGhostBehaviour subclass: #Learning
instanceVariableNames: 'studiedMessages'
classVariableNames: ''
package: 'Ghost-Learning'
Learning>>initialize
super initialize
studiedMessages := Dictionary new.
We add instance variable studiedMessages to store messages and their implementation methods from Object class. When learning will be completed we can inspect this dictionary to analyze required messages.
To process message send our behaviour should retrieve corresponding method from Object class and execute it on ghost instance:
Learning>>send: aMessage to: aGhost
| learnedMethod |
learnedMethod := studiedMessages at: aMessage selector ifAbsent: [ nil].
learnedMethod ifNil: [
learnedMethod := Object lookupSelector: aMessage selector.
studiedMessages at: aMessage selector put: learnedMethod].
"here we should explicitly execute method by primitive to not introduce new messages to ghost because it not what it learn"
^GHMetaMessages executeWith: aGhost andArguments: aMessage arguments method: learnedMethod
Any ghost behaviour should implement message processing method #send:to:.
GHMetaMessages class provides suitable set of methods to execute mirror primitives on given object without sending extra messages to them.
Now to complete our behaviour we need to implement method #currentMetaLevel which defines set of messages which should not be processed by ghost logic. It is answer to second question of any ghost implementation. Here we want to learn all messages which will sent to our ghost:
Learning>>currentMetaLevel
^GHMetaLevel empty
We can also use "GHMetaLevel standard" which will not intercept standard messages from tools. For example #printString or #class will be processed by the meta level instead of ghost behaviour logic. And #class will return class of ghost as any other object. Standard meta level is useful to debug new ghost implementation because tools will look at ghost as any other normal object. And they will not produce recursive ghost behaviour.
But in our case we want to see what exactly inspector doing with ghost. And for this we will use empty meta level.
Last thing which we need to implement is new kind of ghost itself. Let's call it Student:
GHObjectGhost subclass: #Student
instanceVariableNames: 'learning'
classVariableNames: ''
package: 'Ghost-Learning'
Any ghost should implement #ghostBehaviour method and instantiation class side method:
Student>>ghostBehaviour
^learning ifNil: [learning := Learning new]
Student class>>new
^self basicNew
And now we can execute script in workspace:
student := Student new.
student inspect.
And then inspect "student ghostBehaviour studiedMessages":
Improved version of this example is in package Ghost-Learning.
Update: Project was moved to github