rolename.instancemethod() rolename.rolemethod()self in various contexts.
> anObject.roleMethod();
> anObject.instanceMethod();
That's the goal
rolename.rolemethod()
- RoleMethods are very special in that they are tied to the role rather than the object. They can only be invoked explicitly in the code. The rule is here that a RoleMethod has precedence of an instance method.
- The rolename is a synonym for the roleplayer object in all other cases, including the use of
selfin various contexts.
I'm not sure I understand how this injectionless stuff works. Can someone explain it for my simple mind?
I'm not sure I understand how this injectionless stuff works. Can someone explain it for my simple mind?
The following is a long
explanation, but I would like to give the whole story. I have actually
only hacked the compiler in two places with 12 lines of code in the one
and 3 lines of code in the second. These few lines call on my two
classes, both being small extensions of existing Squeak compiler
classes.
The extensions may be
simple, but it took a lot of 'guess and learn' to find them. This post
is not written for simple minds. That's OK, since there are no simple
minds on this list. The hacks are meant to be proof of concept. They
may hopefully give a knowledgeable compiler writer some ideas.
In the following, I build
on the good old money transfer example. I have trimmed it down to three
roles: TransferMoneySource, TransferMoneySink, and Amount. The last is
a method-less role (just a number, in fact). The other two have
RoleMethods. This post explains how the compiler translates the
RoleMethod source code to the runtime.
The complete example
listing is at
http://folk.uio.no/trygver/2011/SqueakExamples/BB5aBank/BB5aBank.html
------ First, the TransferMoneySink role method: ----
deposit
self increase: Amount.
Squeak
has a decompiler that reverse engineers a binary method to create a
synthetic source code. The decompile tells us what the compiler
delivered:
deposit
self increase: (BB1ContextStack playerForRole: #Amount)
I keep the context stack
in what effectively is a global: A class with static variables, static
methods and noting on the instance side. (Forget the BB1 part of my
class names. It is an artifact I use in lieu of a package facility)
Quite simple, so far. The
problem was, of course, to make the compiler generate the above code.
There is an Encoder that transforms source code to nodes for the parse
tree. Many different node classes; the interesting one is class VariableNode.
A VariableNode has a method for generating code for the
variable that needs to be fixed for my purpose.
Given
a class, an Encoder builds VariableNodes for
all variables visible
in the class. Now for my hack: I wrote a subclass of VariableNode
called (BB1)RoleNode that knows what to generate
for me:
(BB1)RoleNode>>asVariable:
roleName contextName: ctxNam.
| arg1 |
comment := nil.
receiver := VariableNode new
name: 'self'
key: #BB1ContextStack -> BB1ContextStack
code: -4.
selector := SelectorNode new comment: nil;
key: #playerForRole: code: -5.
arg1 := LiteralVariableNode new.
arg1 key: roleName asSymbol code: LdLitType negated.
arg1 name: roleName.
arguments := OrderedCollection with: arg1
This magic makes the
compiler generate the required code. Don't ask me how that happens, I'm
not a compiler guy. I can only say that I made an analog of the
superclass VariableNode and it seems to work. That's all.
Now for the Parser.
The Parser initializes the Encoder with
the message
Encoder>>
init: aClass context: aContext notifying: req
--some code--
RoleMethods are compiled
in the context of Traits. So in my special case, the attribute aClass
in the above message signature is a Trait. I added my own stuff
to the method as follows:
Encoder>>
init: aClass context: aContext notifying: req
--some
old code--
" Begin DCI special. "
((class isKindOf: BB1RoleTrait)
and: [class roleContextClass notNil])
ifTrue:
[((class roleContextClass collaboratorsFor:
(class roleContextClass roleNameFromTraitName:
class name))
, #(cc))
do:
[:roleName |
self
scopeTableAt: roleName
put: (BB1RoleNode new
asVariable: roleName
contextName: class
roleContextClassName)]].
" End DCI special. "
--more
old code--
Again, it seems to work.
This concludes the
compiler hacks that support the illusion of a role name being
synonymous with a RolePlayer object.
If you are still with me, we can continue with
------ one of the TransferMoneySource
role methods: ----
run
TransferMoneySource transfer.
Note that the transfer-message
is sent to the role rather than self because self is
ALWAYS the roleplaying object. Here, the programmer explicitly send the
message to the Role and thus invokes the RoleMethod if it exists.
The decompiler tells us
that that the compiler generates this code:
run
(BB1ContextStack playerForRole: #cc)
to: #TransferMoneySource
send: #transfer
withArgs: {}
cc is a
hidden role that is invisible to the programmer and is used by the
compiler . It is always bound to the CurrentContext.
A message to the role is
diverged to the CurrentContext where it picks up the
compiled RoleMethod: (This is in the context superclass, the programmer
does not see it. (This prevents any funny tricks)
(BB1)Context>>to:
roleName send: selector withArgs: argArray
| receiver trait compiledMethod |
receiver := roleMap at: roleName.
trait := self class roleTraitForRoleName: roleName.
" A name convention for Traits makes
this possible. "
trait
ifNotNil: [compiledMethod := trait methodDict at: selector
ifAbsent: [nil]].
compiledMethod
ifNil: [^receiver perform: selector withArguments: argArray]
ifNotNil: [^receiver withArgs: argArray executeMethod:
compiledMethod]
If the compiledMethod is
nil, there is no RoleMethod and we have a normal message send to the
rolePlayer object.
If the compiledMethod exists, we have a Trait method and it is executed in the
context of the RolePlayer object.This is the key to the execution
of RoleMethods without touching the RolePlayer objects or their classes.
What's left is to make the compiler generate the required code. I catch the Parser where it sends a message and add a small hack:
Parser>>messagePart:
level repeat: repeat
--some
existing code--
" Begin DCI special. "
messNodeClass := (encoder classIsTrait and: [receiver isKindOf:
BB1RoleNode])
ifTrue: [ BB1MessageNode]
ifFalse:[MessageNode].
" End DCI special. "
--the rest of the
existing code--
and now this Parser method does what it always does
without knowing anything about my (BB1)MessageNode (A
subclass of existing MessageNode).
Finally, the
following (BB1)MessageNode.method is called from I do not know
where:
(BB1)MessageNode>>receiver: rcvr
selector: selName arguments: args precedence: p from: encoder
sourceRange: range
encoder noteSourceRange: range forNode: self.
^self
receiver: (encoder scopeTableAt: #cc)
selector: #to:send:withArgs:
arguments: (OrderedCollection
with: (encoder encodeLiteral: rcvr roleName)
with: (encoder encodeLiteral: selName)
with: (BraceNode new elements: args))
precedence: p
from: encoder.
Almost identical to the
corresponding superclass method, only it generates what I want rather
than its usual stuff.
That's all, folks. I hope
you enjoyed it.
This concludes the compiler hacks that support the illusion of a role name being synonymous with a RolePlayer object.
I cannot see any way around it without making the distinction visible to the programmer.
This concludes the compiler hacks that support the illusion of a role name being synonymous with a RolePlayer object.
Many of the discussions leading up to this one indicated that a small language change was in order: to change the semantics (and / or maybe the syntax, too) of a Role method invocation from that of a Data object invocation:On Nov 22, 2011, at 5:39 , Trygve Reenskaug wrote:I cannot see any way around it without making the distinction visible to the programmer.
The quoted text at the start of this mail suggests to me that you did not do that — a single syntax suffices.Am I right? If so, I am happy. If not, I have more questions.
In my case it's syntactic sugar. I'm using the same syntax for all method calls RoleMethods and instance methods. The compiler rewrites the former into a call to a static method private to the context
so in my case it's syntactically identical.