Hi, Christophe,

Pardon the repetition if I cover anything you already understand,
but I have a possible (possibly partial?) suggestion below.  This
post is longer than I'd like; it's the result of my exploration
of object contexts.  I didn't have sufficient editing time to
make it more concise.

My conclusions (for anyone who doesn't want to wade through the
background reasoning) are:

*  RT chose the present implementation as the most economical
   way to get object/method behavior, given the unique way
   REBOL implements objects (as private namespaces).  NOTE:
   THIS IS AN INFERENCE ON MY PART, NOT OFFICIAL DOCTRINE!

*  If we understand something of how RT handled functions in
   objects, we can implement a corresponding trick for your
   current problem of embedded methods.

*  There are a couple of ways to do so, one of which is,
   "Don't do that!"

   *  Construct a new object from a context-stripped
      representation of the old object's state.

          new-obj: do mold old-obj

   *  Construct a new object from the same spec used to
      construct the old object.

          old-obj-spec: [ ...blah blah blah...]
          old-obj: make object! old-obj-spec
          ...
          new-obj: make object! old-obj-spec

   The choice between these depends on whether you want any
   changes over the lifetime of OLD-OBJ to be reflected when
   NEW-OBJ is constructed, or whether you want NEW-OBJ to be
   constructed with the same *original* state as OLD-OBJ.

-jn-

"CRS - Psy Sel/SPO, COUSSEMENT Christophe, CPN" wrote:
> 
> ... an example which I've simplified to the following ...
> 

    >> ob1: make object! [
    [    a: 1 + 2 + 3 + 4
    [    e: make object! [
    [        b: 100
    [        f: func [] [a + b]
    [        ]]

It's instructive (to me, as I need instruction beyond what the
available docs cover ;-) to peek under the hood at this point:


    >> source ob1
    ob1: 
    make object! [
        a: 10
        e: 
        make object! [
            b: 100
            f: func [][a + b]
        ]
    ]

The first observation is that OB1/A is shown as being set to a
literal 10, not the original expression that was evaluated to
produce that 10.  The spec block is evaluated during object
construction from that block.

    >> glorp: make object! [
    [    a: 1 + 2 + 3 + 4     
    [    b: first "Yowp!"     
    [    ]                    
    >> source glorp
    glorp: 
    make object! [
        a: 10
        b: #"Y"
    ]

So the "source" for an object is not "the source" from which
it was created, but simply "a source" which represents the
current state of the object.

The second observation is that using an object as the spec
for construction of another object involves using the current
state of the first object as a "guide" for constructing the
second.  Many value types are deep cloned in the new object,
as in:

    >> blob: make object! [
    [    a: 1 + 2 + 3 + 4
    [    b: [[["test"] 1] 2]
    [    ]
    >> blab: make blob []
    >> change blob/b/1/1/1 #"T"
    == "est"
    >> source blab
    blab: 
    make object! [
        a: 10
        b: [[["test"] 1] 2]
    ]

However, function values are handled differently; if functions
in an object are to behave as conventional OO methods, words
in the old function that are defined in the old object's
context must be replaced by words in the new function that
are defined in the new object's context.  (Whew!  What an
awful sentence!)  For example:

    >> w: "Global"
    == "Global"
    >> bb: [w]
    == [w]
    >> ff: func [b [block!] w] [
    [    append b [w]
    [    ]
    >> ff bb "arg"
    == [w w]
    >> ob1: make object! [
    [    fff: func [b [block!]] [
    [        append b [w]
    [        ]
    [    w: "in ob1"
    [    ]
    >> ob2: make ob1 [w: "in ob2"]
    >> ob1/fff bb
    == [w w w]
    >> ob2/fff bb
    == [w w w w]
    >> reduce bb
    == ["Global" "arg" "in ob1" "in ob2"]

So that BB now contains (references to) four words, all of
which happen to be named W, and all of which are defined
in different contexts.  This shows that a (reference to a)
word drags the word's context along with it in order to
make it possible to find that word's value.  (That's also
a sloppy sentence, but in absence of hard documentation,
it's the best I can do at the moment.  I'll be happy to
receive instruction from anyone who can replace it with a
more precise one.)

The point for this thread is that the last two occurrences
of W are supplied by the method FFF in each of the two
objects, which have been made to be relative to the object
in which they occur.

This business of constructing a new function from an old
function that serves as a prototype/template is NOT just
a matter of deep cloning, as the cloned words would still
have their original context.  Instead it must involve the
construction of a new function with a new context.  The
relationship between words in the old and new functions is:

  Old function word/context      New function word/context
  ---------------------------    ---------------------------
  - local to old function        - local to new function
  - in old enclosing object's    - in new enclosing object's
    context                        context
  - otherwise                    - same as old

(And, as always, I'll be grateful for any further light
anyone can shed on this.)

You can see that this is not a trivial change (nor a cheap
one) but is necessary if we are to get the equivalent of
standard OO methods in object-contained functions.  However,
it is expensive enough that we don't want to do it except
where absolutely necessary.

The case of an embedded object is not as strongly compelling
as that of an embedded function, so references to embedded
objects are just cloned (because it's cheaper to do so, is
my conclusion).

Thus, with the original (simplified) example, we get the
expected behavior:

    >> ob1/e/f
    == 110
    >> ob1/a: 20
    == 20
    >> ob1/e/f
    == 120

But using OB1 as the spec to create OB2 caused some surprise:

    >> ob2: make ob1 []
    >> ob2/a: 30
    == 30
    >> ob2/e/f
    == 120

I'll leave it as an exercise for the reader to see how this:

    >> print get first second third second third second ob2
    20

proves that OB2/E/F still refers to OB2/A ;-).

> 
> Ouch, not what I thought... Let's check... In the "Official
> Guide", pg 348, I found:
> 
>     The ancestor object and the descendant object share the
>     same embedded object.
> 

Which we just demonstrated above.

>
> OK, so I tried something else:
> 
> ... example simplified below ....
> 
> Again, not what I thought ... I was awaiting 20 !!! ;-(
>

    >> ob3: make ob1 [
    [    d: make e []
    [    ]
    >> ob3/a: 40
    == 40
    >> ob3/e/f
    == 120

We didn't get 140 (this example's equivalent of your 20),
because of the way OB3/D is constructed.  For reference...

    >> source ob1
    ob1: 
    make object! [
        a: 20
        e: 
        make object! [
            b: 100
            f: func [][a + b]
        ]
    ]

OB3/D is built using OB1/E as a spec.  Therefore, it must
have its own B (which gets cloned) and its own F... AHA!
Since OB3/D/F is a function (see the discussion of making
new object-method functions above), we have to make sure
that words in OB3/D/F are bound to the correct contexts.
Therefore, the B in OB3/D/f *must* refer to the B in
OB3/D -- the immediately-enclosing object for OB3/D/F.

However, the A in OB3/D/F isn't local to OB3/D/F, and isn't
an object field of OB3/D.  REBOL doesn't look any further
for containment, so A is just treated as an ordinary (i.e.,
non-local, non-enclosing-object-field) word and the new A
has the same context as the old A (in OB1/E/F).

> 
> Here are my questions:
> 
> 1. Why did RT choose for such implementation?
>    What are the advantages ?
>

As mentioned above, we *MUST* have context-twiddling for
word in functions that refer to fields of the immediately-
enclosing object, or else we couldn't have object methods.

As to why the same logic isn't propagated upward through
multiple layers of enclosing objects, I can only speculate
that RT:

1)  simply didn't think of it
2)  thought of it but didn't want to incur the expense,
    assuming that it wasn't as compelling a situation, or
3)  implemented the context mechanism in a way that made
    it infeasible to chase up a list of "nested contexts".

>
> 2. Why did my last try not work - it was -IMHO- logical
> I should get a result of 20 ..., as I instanciated the
> embedded object within the instanciation of the ancestor
> object.
>

Because multiple levels of embedding are not traversed.
I hope my analysis of the construction of OB3 answered this
question.  If not (and if you want more of the same tedious
pedantry ;-), let me know.

>
> 2. How can I get my descendant-embedded-object to retrieve
> the property of it's container, the descendant-object ?
> 

I can think of a couple of ways to get this effect.

1)  Construct a new object in a way that strips off context
for all words.  Remember that the "source" of an object is
a representation of how to build an object in the current
state of the one we're looking at.  Therefore, we could say:

    >> ob4: do mold ob1
    >> ob1/a: 99
    == 99
    >> ob1/e/f
    == 199
    >> ob4/e/f
    == 120

When we MOLD OB1, we get a string that represents source for
the current state of OB1.  When we DO that string, we get
an object that is similarly structured to OB1, but *ALL*
words throughout that object and its nested content are now
doing what we might have expected.

2)  Save the original specification, and use it to construct
new objects that are to be "of the same class" as the old
object.

    >> ob-spec: [
    [    a: 0
    [    e: make object! [
    [        b: 100
    [        f: func [][a + b]
    [        ]
    [    ]
    == [
        a: 0 
        e: make object! [
            b: 100 
            f: func [] [a + b]
        ]
    ]
    >> ob5: make object! ob-spec
    >> ob5/a: 43
    == 43
    >> ob5/e/f
    == 143
    >> ob6: make object! ob-spec
    >> ob6/e/f
    == 100

Of course, the difference is that changes over the lifetime
of OB5 will affect only OB5 and not OB-SPEC.  Therefore OB6
starts life as an image of OB5's *original* state, not its
*current* state.  If you really need the current state,
you can use the DO MOLD trick instead.

Thanks for the stimulating questions!

-jn-
-- 
To unsubscribe from this list, please send an email to
[EMAIL PROTECTED] with "unsubscribe" in the 
subject, without the quotes.

Reply via email to