[self-interest] Blocks: Summary

Jecel Assumpcao Jr jecel at merlintec.com
Fri Jun 9 22:22:25 UTC 2000


Dru,

> My implementation is using a bytecode interpreter and will have
> to implement a test on each push. I might make room for another
> type in the references, a block type. That way I could minimize
> the hit of the test to a simple AND rather than a proto lookup.

And don't forget that when you send the 'value' message to the cloned
block, the method must be executed in a slightly different way from all
other methods, so you need yet another test.

I would encourage you to try to think of new ways to think about
blocks. Here is a copy of a message I sent to this list a long time ago
about this (some things can't be understood without the context, but at
least this should give you the flavor of what I am talking about):

Date: Mon, 18 Sep 1995 16:47:00 -0300
From: "Jecel Mattos de Assumpcao Jr." <jecel at lsi.usp.br>
Message-Id: <199509181947.QAA03690 at ofelia.lsi.usp.br>
To: self-interest at self.sunlabs.com
Subject: blocks
content-length: 6611
 
 
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 



More information about the Self-interest mailing list