Mailing List Archive: 49091 messages
  • Home
  • Script library
  • AltME Archive
  • Mailing list
  • Articles Index
  • Site search
 

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