Hi!
In Smalltalk, you'd overwrite #doesNotUnderstand, if you want an object which understands messages to access something like slots you could define at runtime. (Assume you can't use a compiler)
Object subclass: #Cluster instVarNames: 'values' etc.
doesNotUnderstand: aMessage | sel | sel := aMessage selector. ^values at: sel ifAbsent: [ (sel argumentCount = 1 and: [sel first isLetter]) ifTrue: [value at: (sel copyWithout: $:) asSymbol put: aMessage arguments first] ifFalse: [super doesNotUnderstand: aMessage]]
In Self, you could probably do the same, but is there a better way to do this? Why should the VM bother looking up methods which we already know don't exist.
I think, Ian Upright sugested something similar on comp.lang.smalltalk. I'd like to customize method lookup, perhaps similar to this:
cluster _setLookupAgent: ( | :mirror. :selector. slot | slot: mirror slotAt: selector IfAbsent: [mirror parents "etc"] ).
The VM would then call the provided (anonymous) method object instead of using its default algorithm. You could even continue this idea and also allow hooks for method and primitive execution.
A simpler way would be to have a special method, let's say
cluster _setGate: ( | :selector. :arguments. | "..." )
which is called for every message that is directly sent to the cluster object (which is also the receiver of that method). While the gate method is executed, it's disabled to omit recusions. This could be implemented with a simple flag for every object (a hidden slot to store that method (or a well known slotname that's called from inside the VM)) and a counter to temporary disable the mechanism.
Or is the SELF (actually mySelf, as I haven't looked up whether these messages and primitives really exist in Self ;-) solution as simple as
cluster = (| parent = defaultBehavior. "note, no parent slot!" values. "where to store the values" clone = (_clone values: dictionary clone). " doesNotUnderstand: aMessage = ( values at: aMessage selector IfAbsent: [ "etc". "otherwise..." aMessage delegateTo: parent] "forward message to parent" ) |)
This allows us to intercept even inherited messages - something which isn't possible in Smalltalk. The only problem is to hide "parent" and "value", perhaps by renanming them to "hidden_parent" or "my_values".
Thank you for listening, I think I found the answer :-) bye -- Stefan Matthias Aust // Bevor wir fallen, fallen wir lieber auf.
cluster = (| parent = defaultBehavior. "note, no parent slot!" values. "where to store the values" clone = (_clone values: dictionary clone). " doesNotUnderstand: aMessage = ( values at: aMessage selector IfAbsent: [ "etc". "otherwise..." aMessage delegateTo: parent] "forward message to parent" ) |)
This allows us to intercept even inherited messages - something which isn't possible in Smalltalk. The only problem is to hide "parent" and "value", perhaps by renanming them to "hidden_parent" or "my_values".
Thank you for listening, I think I found the answer :-)
I got the following to work as you wanted. It took me a long time to find out how the process object handles undefined messages - it is not very efficient at all :-(
cluster = (|
hidden_parent = defaultBehavior. "as you suggested"
my_values <- dictionary copyRemoveAll. "it is a bad idea to start with nil. And here we can access 'dictionary' for sure, while inside 'clone' it might not work"
clone = (_Clone my_values: my_values copyRemoveAll).
undefinedSelector: sel Type: msgType Delegatee: del MethodHolder: mh Arguments: args = (my_values at: sel IfAbsent: [ 'x' = sel ifTrue: [^1]. sel sendTo: self DelegatingTo: hidden_parent. ]).
|)
I can send 'clone', 'x', 'identityHash' (found in defaultBehavior) and all seems to work. By doing
my_values at: 'f' Put: 9
we can now send 'f' to this object and get the correct answer. Of course, you can see that a *lot* more testing should have been done in the 'undefined...' method (just look at all those arguments we didn't use). Sending 'g' will generate a stack overflow, for example (since delegating to hidden_parent fails, resulting in infinite recursion. And as we all know, the only thing worse than infinite recursion is infinite recursion ;-)
Anyway, while it is nice that this works at all, if you have a reflective implementation (hence the "R" in my upcoming Self/R) where you can get direct access to the message passing mechanism itself, then you can do these kind of things in a much more natural way. You might want to take a look at the Moostrap language (see its entry in the following chart of prototype languages: http://www.slip.net/~dekorte/Proto/Chart.html).
-- Jecel
I got the following to work as you wanted.
Great. However, I think, for mySelf, I'll stick with a single argument #unknownMessage: slot that gets a Message object instead of that multi argument monster of original Self. I understand that they probably wanted to omit the time to create the message transport object, but if you agree that you cannot modify or store that object, you could preallocate one per VM (thread).
Anyway, while it is nice that this works at all, if you have a reflective implementation (hence the "R" in my upcoming Self/R) where you can get direct access to the message passing mechanism itself, then you can do these kind of things in a much more natural way.
How would you do it in Self/R?
You might want to take a look at the Moostrap
I'll do.
bye -- Stefan Matthias Aust // Bevor wir fallen, fallen wir lieber auf.
self-interest@lists.selflanguage.org