[squeak-dev] A question about block closures.

Eliot Miranda eliot.miranda at gmail.com
Wed Jul 8 16:55:28 UTC 2009


On Wed, Jul 8, 2009 at 4:53 AM, David Goehrig <dave at nexttolast.com> wrote:

>
>
> On Wed, Jul 8, 2009 at 2:04 AM, Eliot Miranda <eliot.miranda at gmail.com>wrote:
>>
>>
>>> You could do something analogous with messages and methods an d have the
>> Vm associate the receiver with the activation in... a message send :)
>> If you had a dictionary that cached translations form short forms of
>> selectors, e.g. myJSFunc:with: -> myJSFunc:with:with: then your code might
>> look something like
>>
>> doesNotUnderstand: aMessage
>> "Generic catch all that performs all selectors"
>> (self can: aMessage selector) ifTrue:
>>  [actualSelector := self s: aMessage selector.
>>  ^self perform: actualSelector
>>  withArguments: (aMessage arguments, (Array new: actualSelector numArgs -
>> aMessage selector numArgs))]
>>
>> Would this be better?
>>
>
> Well it it had a prayer of working :)  Here's a little backstory.  The way
> this object works is it inherits from IdentityDictionary has two accessor
> methods:
> has: aProperty of: aValue
>         " This corresponds to object.property = value and
> object['property'] = value"
>         self at: aProperty put: aValue.
>         ^ self.
>
> and
>
> s: aProperty
>        " This corresponds to object.property and object['property']"
>        ^ self at: aProperty ifAbsent: [^ nil ].
>
> For a very limited sets of methods such as these, the ones that can not be
> overridden by end user code, these fields are accessed by doing a message
> send to the object, and the dNU routine determines which route to return
> based on the method can:
>
> can: aSymbol
>      " This corresponds to the javascript:  Object.prototype.can =
> function(k) { return this.hasOwnProperty(k) && typeof(this[k]) == 'function'
> }"
>      ^ ( self s: aSymbol ) isBlock.
>
> What happens in most of the code is objects generate block closures which
> they then set as the values of other object's properties. The code ends up
> looking like this toy example:
> ----
> (An object named: #Casper) has: #greets: of: [ :x | Transcript show: 'Hi ',
> x name , '! My name is ', self name; cr].
> (An object named: #George) from: Casper.
>
> Casper greets: George.
> George greets: Casper.
>
> Casper has: #greets: of: [ :x | Transcript show: 'Goodbye ', x name; cr]
>
> Casper greets: George.
> George greets: Casper.
> ----
>
> And the Transcript then reads:
> ----
> Hi George! My name is Casper
> Hi Casper! My name is George
> Goodbye George
> Hi Casper! My name is George
> ----
>
> The name and named: methods actually set key value and find key by value in
> the Smalltalk dictionary, so they're globals as in editing Javascript's
> window object.
>
> But as you see, I'm passing a block closure to one object, and copying it
> to another object, and both objects are referencing the same block closure!
>  I'm overwriting the
>
> original block closure's outerContext's receiver (which in this example is originally nil!), with whatever object is referencing it.  This is similar to what happens with Javascript
>
> method calls, where the this variable is set by hacking the scope object at call time.  But this lets these objects respond to any message that may come their way, and usually
>
> do the right thing.  My original dNU used the valueWithEnoughArugments: method, because it was the only way I could find to neatly get all the block closures to work in both
>
> the case of over and underflow, as their equivalent functions worked in the JS version.
>
> If I compile an actual method send, and dNU is not called, I then need to
> always compile a method every time a property is
> set, even if it is never executed.
>
> In this application, objects inherit based on a shallow copy of object properties.  So
> you may inherit some methods using from: from one object, a bunch of others
> from another,
> and then override some of each of those in a third.  (This scenario
> actually the norm not the exception btw!)  99% of the time, I want whatever
> the values the block closure
> derived from its original defining context, with the sole exception of the
> outerContext's receiver, which is almost always wrong for my purposes.
>
> I'm mostly wondering if there's a cleaner, more optimizeable way of messing
> around with the internals of either the resulting MethodContext or your
> closure objects :)
>

Well the obvious way to clean up those closures is to pass the self
reference as an argument, so

(An object named: #Casper) has: #greets: of: [ :theSelf :x | Transcript
show: 'Hi ', x name , '! My name is ', theSelf name; cr].

and

doesNotUnderstand: aMessage
"Generic catch all that performs all selectors"
(self can: aMessage selector) ifTrue:
[^( self s: aMessage selector ) valueWithEffectiveReceiver: self
andEnoughArguments: aMessage arguments ].
^ self s: aMessage selector.

and for a little bit of extra efficiency I would combine can: and s:, e.g.

doesNotUnderstand: aMessage
"Generic catch all that performs all selectors"
^(self s: aMessage selector)
ifNotNil: [:block| block valueWithEffectiveReceiver: self
andEnoughArguments: aMessage arguments]
ifNil: [self s: aMessage selector]


As regards more generic property handling I don't know of a better way than
the doesNotUnderstand: handler for handling properties held in per-instance
dictionaries if the constraint is that you definitely don't want to compile
accessors for the property and you are using the vanilla VM.  You would
indeed need to modify the VM to provide for a cheap property accessor, for
example, being able to add something as the value in a method dictionary
that said "invoke this selector with the actual selector as an additional
argument", so in the method dictionary you would have (avoiding support for
varargs)

    myProperty -> { #getProperty: } asPropertyAccessorThingy
    myProperty: -> { #setProperty:to: } asPropertyAccessorThingy
    myMoreGenericFunction:with: -> { #evaluateGenericProperty:with:with:
} asPropertyAccessorThingy

etc and

MyPrototypeObject getProperty: propertyName
         ^properties at: propertyName ifAbsent: [parent getProperty:
propertyName]

etc


But methods are cheap and so I don't see the benefit of avoiding methods in
favour of blocks.  When one uses mehtods all sorts of optimizations are
enabled, such as efficiently mapping high-dynamic-frequency properties to
slots a la V8.  Vassili Bykov, Claus Gittinger and I did a prototype
JavaScript implementation in Smalltalk in the summer of 2006 which did this
and it was fast.  Vassili came up with the great idea of generating bytecode
for slot accessors directly and minting a set of 256 accessors that
traversed the parent chain in a loop.  Effectively each accessor method
looked like

accessSlot0
    | object |
    object := self.
    [object slot0 ifNotNil: [:value| ^value].
     (object := object parentSlot) ~~ nil] whileTrue.
    ^#undefined

The closure bytecode pushTemp:inVectorAt: can be used to fetch an arbitrary
slot of an object held in a temporary and so is used to implement object
slotN and object parentSlot.

Both Claus Gittinger and Dan Ingalls have had the idea of creating an object
that is an instance of itself.  One way, without VM mods (and ignoring
issues with compact classes), is to do something like

prototype: aPrototype numNewSlots: numNewSlots
    | format object |
    object := (Array new: aPrototype numberOfSlots + numNewSlots + 3).
    object
         at: 1 "superclass" put: aPrototype;
         at: 2 "methods"    put: MethodDictionary new;
         at: 3 "format"       put: (self formatOf: aPrototype format
withAdditionalSlots: numNewSlots).
   object changeClassTo: object.
   ^object

If one flattens MethodDictionary into an array of pairs, which is good for
space and even faster for lookup for most dictionaries, because they're
small, and linear scan is fast, then nil looks like an empty method
dictionary.  Then a primitive implementation is no more than a single
allocation that fills in the prototype and format slots and sets the class.

Combine one with the other and you have the potential for a lean and mean
prototype-based system using the existing VM technology, which would mean an
efficient JavaScript implementation within Smalltalk.

so much to do and so little time ;)

best
Eliot

Dave
>
>
> --
> -=-=-=-=-=-=-=-=-=-=- http://blog.dloh.org/
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20090708/f3aa160b/attachment.htm


More information about the Squeak-dev mailing list