[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
blocks
Re-reading the ECOOP proceedings this weekend I was surprised
to find out that I hadn't read "Programming as an Experience:
The Inspiration for Self". I could hardly believe it as I was
so sure I had read it twice already, but it seems I got it
confused with some other paper ( it happens as we get older ;-).
Anyway, it sure is great stuff! And all of the issues that
I have raised here these last few days were mentioned in the
paper and there were even some good answers. Rather than taking
this as a sign that my idle speculations are not getting us
anywhere, I couldn't resist taking a look at blocks.
The story for blocks seems a little strange, as the following
table shows:
outer method = normal methods stored in constant slots
block = the literal block object
block context = the result of pushing a block on the stack
value = the value method inside the block activation object
inner method = literal anonymous methods used in Self 1.0
outer method block blockContext value inner method
-----------------------------------------------------------------------------
clones when
fetched from
const slot YES -- -- YES --
can be stored
in data slot NO NO YES NO NO
clones when
fetched from
data slot -- -- NO -- --
clones when
pushed on
stack -- YES -- -- YES
new
slot name :self* -- <scope> <lexParent>* <lexParent>*
new
slot value receiver -- thisContext same as thisContext
blockContext
<scope>
I found it very hard to justify the way that the value method of a block
currently works. I could reduce blocks to mere syntactic sugar ( at the
very high cost of using four temporary objects rather than the current
two ) if Self had these two features:
1) local methods with the semantics of the old inner methods
2) the ability to manipulate running and dead Activations without
mirrors
Item one means that the "silly" example I gave before would return
2 instead of 4, but the "self marker" would not move as Randy
proved it shouldn't. The idea is that a method activation is a
refinement of an object, so a method defined within this refinement
should result in a refinement of the refinement rather than in a
refinement of the original object. This is a very forced view of
things, but is not totally unreasonable. The "self marker" stays
in place by having a <lexParent>* slot instead of a :self* one.
I already have item two in tinySelf. This can be explained in terms
of the implementation ( which is how I answered Mario when he asked
why my "thisContext = (self)" example didn't run into infinite
recursion ) or by looking into "slot types" as Mark explained about
Glyphic Script. Here is a third alternative to explain this:
I don't like that when a method object is cloned to produce an
activation a ":self*" slot must be added. I want the method
object to have this slot too, so it is a real prototype of the
activation. Of course, its argument slots don't have any valid
values yet and this would be nasty in the case of a parent slot
like ":self*".
What happens when we fetch an object from a slot? We can imagine
that it is sent an "Eval" meta-message. For most objects, this
would just return the object itself. For methods, it would clone
it and fill in the arguments and schedule it for execution.
What if this were achieved by placing a special value in the
":self*" slot so that the method object would inherit this cloning
behavior? In that case, once the arguments were filled in ( including
the self slot ) then the activation could be stored in data slots
and fetched without cloning - it would now inherit from a "normal"
object and would handle "Eval" like normal objects - returning
itself rather than a clone.
So we have that in Self 4.0 activations are not true clones of
methods, but both handle "Eval" in the same way. In tinySelf
activations are true clones of methods, but handle "Eval" differently
due to a different value in the dynamic parent slot. TinySelf is
backwards compatible, however, as it is not possible to store
an activation in a slot in Self 4.0 to see the difference.
Now, supposing the above hasn't made you too sick to proceed :-)
globals _AddSlots: ( | blockActivationProto = () | )
blockActivationProto _Define: ( | parent* = traits block.
scope | )
traits block _AddSlots: ( | value = (scope value) | )
( | test = ( | i <- 0.
b1 = ( | value = ( (i * i) printLine ).
bap = blockActivationProto |
bap clone scope: _ThisActivation ).
b2 = ( | value = ( i factorial printLine ).
bap = blockActivationProto |
bap clone scope: _ThisActivation )
| .........
.........
.........
... ifTrue: b1 False: b2.
.........
)
| ) test
This code causes the following four temporary objects to be
create per block use:
(1) a clone of b1 is created when it is fetched from its slot in the
"... ifTrue: b1 ..." part of the code. It has a <lexParent>* slot
( rather than self* ) that is set to the current activation.
(2) a clone of blockActivationProto is created when (1) is executed
and its scope slot is set to (1).
(3) a clone of the "value" method is created when the ifTrue:False:
sends this message to (2). The method is found in traits block
and the self* slot is set to (2).
(4) a clone of the "value" method is created when (3) executes. The
method is found in (1) and the <lexParent>* slot is set to
(1).
Of course, not only are four objects a bit too much for a thing
like this, but we also have a problem that (4) inherits from
(1) which inherits from the original method activation ( which
has the "i" slot, for example ). This means that (4) sees the
"value" and "bap" slots in (1) ( but this could be "fixed" by
using weird names like was done with "<lexParent>*" ).
Please note that this could be done with mirrors in the b1 and
traits block value methods if we didn't have the item 2)
above, but this would mean yet another temporary object.
While this is clearly not the solution to the "blocks problem",
I think the answer is somewhere in that direction.
Cheers,
-- Jecel