Embedded Object and Scope - again...
[1/9] from: coussement:c:itc:mil:be at: 9-May-2001 10:53
Hi list:
----------------------------------------------------
Sorry for insisting, but it's the first time one of my question stays
unanswered, and I need the answer badly ;-P
I still hope somebody will help me (perhaps at RT) so I repost my
question...
----------------------------------------------------
I tried the following:
<snip>
ancestor-object: make object! [
global-obj-prop: make integer! 0
init: func [
val [integer!]
][
global-obj-prop: make integer! val
]
embedded-object: make object! [
print-global-obj-prop: does [print global-obj-prop]
]
]
</snip>
Ok, so I tried:
>> ancestor-object/init 10
== 10
>> ancestor-object/embedded-object/print-global-obj-prop
10
No problem here :-)
>> descendant-object: make ancestor-object []
>> descendant-object/init 20
== 20
>> descendant-object/embedded-object/print-global-obj-prop
10
Ouch, not what I thought... Let's check... In the "Official Guide", pg 348,
I found:
<quote>
The ancestor object and the descendant object share the same embedded
object.
</quote>
OK, so I tried something else:
>> descendant-object: make ancestor-object [descendant-embedded-object: make
embedded-object []]
>> descendant-object/init 20
== 20
>> descendant-object/descendant-embedded-object/print-global-obj-prop
10
Again, not what I thought ... I was awaiting 20 !!! ;-(
Here are my questions:
1. Why did RT choose for such implementation ? What are the advantages ?
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.
2. How can I get my descendant-embedded-object to retrieve the property of
it's container, the descendant-object ?
Thx for answering :-))
chr==
[2/9] from: arolls::bigpond::net::au at: 10-May-2001 3:53
I really don't know the answer.
I think we need a make/deep or something.
I think the embedded-object words are still bound to
the ancestor-object context. That means global-obj-prop
is still bound to the ancestor-object.
Does this modification help?:
print-global-obj-prop: func [obj][
print get in obj 'global-obj-prop
]
; try it out
ancestor-object/init 10 ; == 10
ancestor-object/embedded-object/print-global-obj-prop
ancestor-object ; == 10 - good
;try it out again
descendant-object/init 20 ; == 20
descendant-object/embedded-object/print-global-obj-prop
descendant-object ; == 20
Anton.
[3/9] from: coussement:c:itc:mil:be at: 10-May-2001 11:48
> I really don't know the answer.
> I think we need a make/deep or something.
<<quoted lines omitted: 14>>
> descendant-object ; == 20
> Anton.
[
Thanks for the reply Anton.
Your proposal shouldn't work in the context I have to use it.
I'm still wondering why RT choose such an implementation ...
Thanks anyway,
CU
chr==
]
[4/9] from: arolls:bigpond:au at: 10-May-2001 22:49
I am wondering too.
I think if I study Ladislav's document he published earlier
today then we might be able to hack something in.
Anton.
[5/9] from: joel:neely:fedex at: 10-May-2001 9:11
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-
[6/9] from: joel:neely:fedex at: 10-May-2001 13:19
While discussing this with a collegue, I had a couple of added
insights.
Joel Neely wrote:
...
> As to why the same logic isn't propagated upward through
> multiple layers of enclosing objects, I can only speculate
<<quoted lines omitted: 4>>
> 3) implemented the context mechanism in a way that made
> it infeasible to chase up a list of "nested contexts".
That may sound harsher than I meant -- I wasn't criticizing,
but just trying (poorly) to say that what one can do easily
with one mechanism may or may not be easy or obvious or even
useful if one has a different conceptual mechanism.
I should also have added another option:
4) the entire concept of "embedded object" isn't really
meaningful to REBOL.
Consider this transcript:
>> obja: make object! [
[ a: 1
[ b: "hi"
[ f: func [] [print a]
[ ]
>> objb: make object! [
[ c: 10
[ d: "there"
[ x: obja
[ g: func [][print [x/b d c + x/a]]
[ ]
>> objb/g
hi there 11
>> objc: make objb [c: 20 d: "you guys"]
>> objc/g
hi you guys 21
>> obja/a: 100
== 100
>> objb/g
hi there 110
>> objc/g
hi you guys 120
>> source objc
objc:
make object! [
c: 20
d: "you guys"
x:
make object! [
a: 100
b: "hi"
f: func [][print a]
]
g: func [][print [x/b d c + x/a]]
]
Now, let't think about these questions:
1) In what sense do OBJB and OBJC "contain" OBJA?
2) In this case, don't we want OBJB and OBJC to "share"
a single OBJA (even though we don't retain its
global name)?
3) How is this different from either of the cases below?
CASE 1: "EMBEDDED" OBJECT
>> obje: make object! [
[ c: 20
[ d: "yawl"
[ x: make object! [
[ a: 2
[ b: "greetings"
[ f: func [] [print a]
[ ]
[ g: func [][print [x/b d c + x/a]]
[ ]
>> obje/g
greetings yawl 22
>> objf: make obje [c: 30]
>> objf/g
greetings yawl 32
CASE2: "PRIVATE" OBJECT
>> use [secretobj] [
[ secretobj: make object! [
[ a: 3
[ b: "howdy"
[ f: func [] [print a]
[ ]
[ objg: make object! [
[ c: 30
[ d: "youse guys"
[ x: secretobj
[ g: func [][print [x/b d c + x/a]]
[ ]
[ ]
>> objg/g
howdy youse guys 33
>> objh: make objg [a: 40 d: "aloha"]
>> objh/g
howdy aloha 33
The fact that an object is created concurrently with an
enclosing
object is time-wise coincidental, except when
the "inner" object contains words that refer to members
of the "outer" object without explicit qualification.
> >
> > 2. How can I get my descendant-embedded-object to retrieve
<<quoted lines omitted: 3>>
> 1) Construct a new object in a way that strips off context
> for all words.
Of course this fails if you *DO* want some sharing -- if you
want the new object simply to refer to a third party also
referenced within the old object.
-jn-
[7/9] from: gjones05:mail:orion at: 10-May-2001 18:07
From: "CRS - Psy Sel/SPO, COUSSEMENT Christophe, CPN" > Hi list:
<snip> I tried the following:
> <snip>
> ancestor-object: make object! [
<<quoted lines omitted: 21>>
> 10
> Ouch, not what I thought... Let's check... In the "Official Guide", pg
348,
> I found:
> <quote>
<<quoted lines omitted: 3>>
> OK, so I tried something else:
> >> descendant-object: make ancestor-object
[descendant-embedded-object: make
> embedded-object []]
> >> descendant-object/init 20
<<quoted lines omitted: 4>>
> Here are my questions:
> 1. Why did RT choose for such implementation ? What are the advantages
?
> 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.
> 2. How can I get my descendant-embedded-object to retrieve the
property of
> it's container, the descendant-object ?
>
> Thx for answering :-))
>
> chr==
Hi, Christophe,
CC> Why did RT choose for such implementation ? What are the advantages
?
A social-anthropology professor once warned us students against asking
why
questions -- the answers tend to be too grounded in philosophical
tautologies. I evidently didn't learn my lesson, because I still ask
them 20+ years later. ;-)
I suspect that this behavior is partially a legacy of the way in which
RT assigns values in memory. I can't think of a really good advantage,
mainly because any argument that I come up with fails the principle of
least surprise. The path notation suggests that something is being
pointed to that really isn't!
CC> 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.
If you will probe the object created by the last try, you will see that
it contains two objects: the original embedded object and a new
descendant embedded object. As far as I can tell, this demonstrates
that making a descendant object does not really instantiate a unique
instance of embedded objects. However, it does create new instances of
the embedded word values, like global-obj-prop. Through the original
series of examples, you create an object, then you create an object
derived from the original object. The global-obj-prop is unique in each
of these objects, but the embedded object only exists in the original
object creation. This is the reason that Anton's suggestion provided a
nice work around to the problem.
What I suspect that you are looking for is the way to create new words
in the context of the newly made object. See the next section for "the
way".
CC> How can I get my descendant-embedded-object to retrieve the property
of
it's container, the descendant-object ?
Luke, use The Force,
or at least the 'context. I believe that
'context may be the solution to your problem.
>> ? context
USAGE:
CONTEXT blk
DESCRIPTION:
Defines a unique (underived) object.
CONTEXT is a function value.
ARGUMENTS:
blk -- Object variables and values. (Type: block)
Now, back to your example. Now, the ancestor sets up a unique context
for the property. Creating a new descendant object will create a new
context for this property.:
ancestor-object: make object! [
global-obj-prop: context [the-obj-val: 0]
init: func [
val [integer!]
][
global-obj-prop/the-obj-val: make integer! val
]
embedded-object: make object! [
print-global-obj-prop: does [print
global-obj-prop/the-obj-val]
]
]
ancestor-object/init 10 ;yields == 10
ancestor-object/embedded-object/print-global-obj-prop ;yields 10
; Christophe is happy so far. Now for the moment of truth ...
descendant-object: make ancestor-object []
descendant-object/init 20 ; yields == 20
descendant-object/embedded-object/print-global-obj-prop ;yields 20
; now Christophe is very, very happy!
I'm not saying that this all makes total sense to me, but once I began
to understand how the code is represented in memory, I began to
understand how to deal with the problem that you laid out. I hope that
this is of some benefit.
--Scott Jones
[8/9] from: larry:ecotope at: 10-May-2001 17:42
Hi Scott
> "Luke, use The Force," or at least the 'context. I believe that
> 'context may be the solution to your problem.
<<quoted lines omitted: 6>>
> ARGUMENTS:
> blk -- Object variables and values. (Type: block)
Good to keep in mind that CONTEXT is just a shortcut for MAKE OBJECT!
context: func [
"Defines a unique (underived) object."
blk [block!] "Object variables and values."
][
make object! blk
]
Of course, your example works just fine with CONTEXT replaced with MAKE
OBJECT!
-Larry
[9/9] from: gjones05:mail:orion at: 10-May-2001 21:58
From: "Larry Palmiter"
> Hi Scott
> > "Luke, use The Force," or at least the 'context. I believe that
<<quoted lines omitted: 7>>
> ]
> Of course, your example works just fine with CONTEXT replaced with
MAKE
> OBJECT!
Uh oh, I guess the charlatan has been exposed! Burn him at the stake.
Oh, that's me I'm talking about. Never mind. Thank goodness that
context *is* just make object! in sheep's wool; I thought there was
something unique in the way it specifed "unique (underived)." Your
clarification makes it plain to see. Thanks.
Oh, a rose by any other name...
Say, goodnight, Scott. Goodnight, Scott.
--Scott Jones
Notes
- Quoted lines have been omitted from some messages.
View the message alone to see the lines that have been omitted