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

Embedded Object and Scope yet again...

 [1/13] from: larry:ecotope at: 11-May-2001 13:31


Hi Joel You raise a lot of interesting questions. I will respond in several short posts. We were discussing using a quick object wrapper around script code: REBOL [] ctx-wrapper [ ........ all my code here ] end ctx-wrapper
> > ... There are some extra precautions when using vid which I will skip > > here. > > > > Do tell?!?! Precautions are A Good Thing! >
OK, here are the extra precautions and the reason why. Consider the following code: ctw-wrap: context [ layo: layout [ rbb: box red button "Change color" [rbb/color: random 255.255.255 show rbb] ] view layo ] After it runs, we find:
>> type? rbb
== object! Even though LAYOUT creates a face object, all set-words used in the block arg to LAYOUT are bound in the global context. This is not true for STYLIZE. So we have to add place-holders for all set-words used in the layout block(s) to the wrapper object, like so: ctx-wrap: context [ ; place-holders for all set-words in layout block(s) rb: none ; normal object variables a: [1 2 3] double: func [x][x * 2] layo: layout [ rb: box red button "Change color" [rb/color: random 255.255.255 show rb] ] ; some code view layo ] After this runs, we get:
>> rb
** Script Error: rb has no value ** Near: rb
>>
More soon -Larry

 [2/13] from: lmecir:mbox:vol:cz at: 11-May-2001 22:30


Hi, I feel, that Joel tried to warn us, that the "Poor man's module" is not as reliable as some may need it to be: Rebol [] make object! [ protected1: 1 protected2: 2 if true [ unprotected1: 3 ] ] and he is right, because 'protected1 and 'protected2 will not destroy their global context couterparts, but 'unprotected1 can!

 [3/13] from: gjones05:mail:orion at: 11-May-2001 16:03


From: "Ladislav Mecir"
> Hi, > > I feel, that Joel tried to warn us, that the "Poor man's module" is
not as
> reliable as some may need it to be: > Rebol []
<<quoted lines omitted: 6>>
> ] > and he is right, because 'protected1 and 'protected2 will not destroy
their
> global context couterparts, but 'unprotected1 can!
I feel like I'm loosing ground in my understanding of REBOL objects. My whole understanding has been turned upside down in less than 24 hours. Of course, this means that I really didn't understand them at all. It also means that it clearly violates the principle of least surprise --- at least for me. How depressing. --Scott Jones

 [4/13] from: larry:ecotope at: 10-May-2001 23:10


Hi Joel You have been coming up with some fun examples lately. Maybe I can throw a little light on the topic. First the effect in your example is also obtained with this code: ob1: make object! [ a: 1 init: func [n [integer!]] [a: n] set 'ob11 make object! [ b: 10 init: func [n [integer!]] [b: n] wot: func [] [a + b] ] ] All of your tests below give the same result as when using the do [] construct. The form above is the poor man's module capability (until the real thing comes along in REBOL/Core 3). Carl, myself, and many others often use this construction. In fact, much of VID is written using this mechanism. For example, if you do source LAYOUT, you will find that layout calls a number of functions like TRACK, EXPAND-SPECS, DO-FACETS, GROW-FACETS, etc. which are not defined globally. This works because LAYOUT was defined in the VID object, but it was defined using SET as above and thus exported to the global context. This is very useful because it allows you to export a few interface functions for the object to the global context, while at the same time being able to reference all of the words in the "parent" object. At the same time, the global context is shielded from the many named data-structures, layouts, sub-objects, styles, helper functions, etc. which are included in the parent object. So how does all this work? There are others who can surely give a better explanation than I can, but I will make a few comments. There are stages in object creation, and in one of the early stages the object spec block is scanned for all first-level set-words. A context for the object is created with these words bound to it. After this stage, the code in the object spec is executed, in exactly the same way as a block of code in the global context. The only difference is that those words which occurred in the spec-block as set-words are bound to the objects context. Any code can appear and will be executed. For example:
>> x: 21 ob: make object! [ a: 52 print [a x]]
52 21 This feature is also very handy. I often wrap a whole script this way: REBOL [] ctx-my-script [ .... all of the script code just as it was when its words were in the global context ] end ctx-my-script The global context is protected from all of the set-words used in the script. There are some extra precautions when using vid which I will skip here. There are a couple of facts about "embedded" objects which are good to know: 1) Sub-objects which are defined with a set-word in the parent object are treated differently than other datatypes when the object is cloned. Sub-objects are not cloned, the pointer (which what a word really references) still points to the original sub-object. Other datatypes are copied into the cloned object. . 2) Sub-objects exported with your do [] construct or by using SET are not really "embedded", the words which reference them are global, but the words contained in their definition blocks are bound to the context of the object in which they are created. To me it is similar to this example:
>> b: [] repeat j 3 [use [x][x: j append b 'x]]
== [x x x]
>> b
== [x x x]
>> reduce b
== [1 2 3] The word b is in the global context, it's value is a block which contains the words x, x, and x each of them created in a different unnamed context. You can do much the same thing in LISP, Scheme or other symbolic programming languages. Your conclusion can be extended to state that it is often misleading to apply the concepts of object-oriented programming in trying to understand REBOL. REBOL's objects do not support the key concepts of OO. They are primarily name-spaces, and have their own distinctive properties relating to the underlying symbolic language. They are one-of-kind objects, sometimes called objects by prototype in the books. Perhaps others will contribute further insights or corrections. (Carl, Jeff, Holger, Gabriele, Ladislav?) -Larry

 [5/13] from: gjones05:mail:orion at: 11-May-2001 6:49


From: "Larry Palmiter" <major snip>
> There are a couple of facts about "embedded" objects which are good to
know:
> 1) Sub-objects which are defined with a set-word in the parent object
are
> treated differently than other datatypes when the object is cloned. > Sub-objects are not cloned, the pointer (which what a word really > references) still points to the original sub-object. Other datatypes
are
> copied into the cloned object. > . > 2) Sub-objects exported with your do [] construct or by using SET are
not
> really "embedded", the words which reference them are global, but the
words
> contained in their definition blocks are bound to the context of the
object
> in which they are created. To me it is similar to this example:
These are good rules to remember and add some real meat to the potatoes. I am still struck by how none of this is explicitly obvious. I feel positively evangelical about REBOL, but this area is one of those areas that makes me feel particularly nervous, like the only way that I feel like I would know what I was doing would be to always do some major experimenting and value checking along the way. I don't believe that I could write a section of code in these techniques and expect it to run right the first time. Most of REBOL does work this way for me, meaning it runs the way I would expect. I guess that this area will be one of those less-intuitive paradigms to be learned and integrated.
> >> b: [] repeat j 3 [use [x][x: j append b 'x]] > == [x x x] > >> b > == [x x x] > >> reduce b > == [1 2 3]
This looks like total magic. I actually felt compelled to rerun the example to be sure that it works (not that I didn't believe you, but it was like wanting to see a magic trick again). Conceptually, I can understand what is happening, but, wow. I keep trying to figure out how this code is represented in memory.
> The word b is in the global context, it's value is a block which
contains
> the words x, x, and x each of them created in a different unnamed
context.
> You can do much the same thing in LISP, Scheme or other symbolic
programming
> languages.
I guess that this is the critical point for me: I've had very little experience in using truly symbolic languages. <remainder snipped> Thanks, Larry, your additional discussion has helped to shed further light on this topic. Regarding intuition, I am reminded of a medical school professor who repetitively said in lectures that this, that and the other principle were "intuitively obvious" and therefore required no further lecture time for explaination. We students sat in stunned silence. I realized that anything becomes intuitively obvious after working in a subject area for 20+ years. It was John Dvorak (computer tech pundit) who first articulated this concept, that intuition was largely a learned experience. I think he is right. I await the day when REBOL scoping rules and parsing become intuitively obvious. --Scott Jones

 [6/13] from: joel:neely:fedex at: 11-May-2001 7:58


Hi, Larry, Thanks for the very clear descriptions, and more than "a little" light. (I wish I could always be so succinct! ;-) Larry Palmiter wrote:
> Hi Joel > You have been coming up with some fun examples lately. Maybe I can
<<quoted lines omitted: 14>>
> others often use this construction. In fact, much of VID is written > using this mechanism.
Maybe in this case I was *too* succinct, as I really had a reason for using the DO block instead of SET, but ran out of time to cover the distinction. IMHO, using DO or USE is a more general mechanism, as it actually allows you to "embed" references wherever you wish, instead of being limited to the global level as with SET. For example:
>> o-top: make object! [
[ a: 1 [ init: func [n][a: n] [ tunnel: none ;; placeholder [ o-middle: make object! [ [ b: 10 [ init: func [n][b: n] [ use [temp] [ [ temp: make object! [ [ c: 100 [ init: func [n][c: n] [ wot: func [][a + b + c] [ ] [ tunnel: temp [ ;; but we could have put [ ;; a ref to temp anywhere! [ ] [ ] [ ]
>> source o-top
o-top: make object! [ a: 1 init: func [n][a: n] tunnel: make object! [ c: 100 init: func [n][c: n] wot: func [][a + b + c] ] o-middle: make object! [ b: 10 init: func [n][b: n] ] ]
>> o-top/tunnel/wot
== 111
> This is very useful because it allows you to export a few interface > functions for the object to the global context... >
I'm suggesting that the DO or USE trick is even more useful because it allows you to locate the ob refs or interface functions *anywhere* in the nested collection of namespaces under construction. Of course, as with any highly powerful tool, it's easy to end up constructing logical Gordian knots that surpass all untangling!
> ... in one of the early stages the object spec block is scanned for > all first-level set-words. A context for the object is created with
<<quoted lines omitted: 3>>
> difference is that those words which occurred in the spec-block as > set-words are bound to the objects context...
I'd only add that the process you described above apparently is nested nicely when another MAKE OBJECT! ... is encountered within the execution of the spec block.
> This feature is also very handy. I often wrap a whole script this way: > > REBOL [] > ctx-my-script [ > ... all of the script code ... > ] end ctx-my-script >
] ; end ctx-my-script Yes, that's my stereotypical script layout as well, although I often end the object with something like .... run: func [...] [...] ] ; end of object so that I can DO the script once within another script and re-run it as needed without reloading.
> ... There are some extra precautions when using vid which I will skip > here. >
Do tell?!?! Precautions are A Good Thing!
> There are a couple of facts about "embedded" objects which are good > to know: >
I guess the key point I was trying to make in my earlier post (which I didn't state very well) is that the *conventional* use of the term embedded implies the following: If object EO is embedded in a parent object PO, then: 1) EO has visibility into the namespace of PO, and 2) PO contains a reference to EO (at the class or instance level). However, that simply isn't the case with REBOL. Using SET, or the more flexible USE or DO, with the definition of PO allows us to create different flavors of EO in which the two characteristics above can be manipulated independently. In other words, we can create any (or all) of the following cases: EO doesn't know words in PO PO doesn't contain a ref to EO EO does know words in PO PO doesn't contain a ref to EO EO doesn't know words in PO PO does contain a ref to EO EO does know words in PO PO does contain a ref to EO (Of course, when I speak of "knowing" words, I mean the ability to use the words *as words in the specified context* and not just the ability to get to their content via a path.) Therefore, it may not be useful to talk in terms of "embedded" objects at all. (When I was discussing the concepts in this thread with a collegue yesterday, she observed, "This would give you a way to implement the equivalent of 'friend' from c++", thought a moment, and then added, "But why would anyone want to do that???" ;-)
> Your conclusion can be extended to state that it is often misleading > to apply the concepts of object-oriented programming in trying to
<<quoted lines omitted: 3>>
> one-of-kind objects, sometimes called objects by prototype in the > books.
ABSOLUTELY (and well said)! Which is why I have minor heartburn over the way in which existing writings (including this list) sometimes use well-established terms from comp sci, but use them with distinctly non-standard meanings. That can serve to increase barriers to the wider understanding and use of REBOL, IMHO. Let me be clear: it's A Good Thing for REBOL to be able to introduce ideas and mechanisms that are "out of the box" of conventional languages. We just need to have vocabulary/definitions/explanations that make it clear when that is happening, else we risk miscommunication and misunderstanding. And, to the extent that some of those "key concepts of OO" have to do with performance and resource utilization, I thing our community needs to take a close look at where the scalability and performance issues arise, and to be reasonable in our claims about REBOL. Thanks for clarifying the conclusion I've been hinting at but didn't manage to express with sufficient force or clarity. Must be the caffeine deficiency! -jn-

 [7/13] from: joel:neely:fedex at: 10-May-2001 23:13


Hi, all! Just a small further demonstration that the term "embedded object" is of questionable significance in REBOL...
>> ob11: none
== none (just to make sure that there's nothing up my sleeve...)
>> ob1: make object! [
[ a: 1 [ init: func [n [integer!]] [a: n] [ do [ [ ob11: make object! [ [ b: 10 [ init: func [n [integer!]] [b: n] [ wot: func [] [a + b] [ ] [ ] [ ] So where does that leave us?
>> source ob11
ob11: make object! [ b: 10 init: func [n [integer!]][b: n] wot: func [][a + b] ]
>> source ob1
ob1: make object! [ a: 1 init: func [n [integer!]][a: n] ] But notice what A in OB11/WOT refers to!
>> ob11/wot
== 11
>> ob1/init 3
== 3
>> ob11/wot
== 13
>> ob11/init 20
== 20
>> ob11/wot
== 23 So, is OB11 an "embedded object" in OB1 or not? PRO: It contains a word (A) belonging to the context of OB1. It was created during the creation of OB1. CON: OB1 contains no reference to OB11. OB11 is, in fact, a global name. Maybe the question contains too much baggage in the phrase embedded object , eh wot? -jn-

 [8/13] from: larry:ecotope at: 11-May-2001 16:20


Hi Ladislav
> I feel, that Joel tried to warn us, that the "Poor man's module" is not as > reliable as some may need it to be:
<<quoted lines omitted: 7>>
> ] > and he is right, because 'protected1 and 'protected2 will not destroy
their
> global context couterparts, but 'unprotected1 can! >
Your example illustrates that: 1) code in an object definition is executed 2) within an object definition we have access to all parent contexts up to and including the global context. It is consistent with my own (perhaps poorly worded) description: [ There are stages in object creation, and in one of the early stages the object spec block is scanned for all first-level set-words. A context for the object is created with these words bound to it. After this stage, the code in the object spec is executed, in exactly the same way as a block of code in the global context. The only difference is that those words which occurred in the spec-block as set-words are bound to the objects context. Any code can appear and will be executed. ] UNPROTECTED1 is not a first-level set-word, so it is not in the objects context and thus no protection of global namespace is implied. The IF statement is just a piece of code which is executed when the object is created, it has access to the global context and can define or redefine any global (or object variable). It seems to me that we have pretty good control over our contexts. If we want UNPROTECTED1 to not affect the global context, we just add UNPROTECTED1: none in our object, making it a first-level word. REBOL currently is based on a context hierarchy, where nested funcs and objs have access to words in all the higher levels of the hierarchy, as well as those in their own context. Modules, as Carl has described them will allow control of both imported and exported variables. -Larry

 [9/13] from: larry::ecotope::com at: 11-May-2001 17:26


Hi Joel
> IMHO, using DO or USE is a more general mechanism, as it actually > allows you to "embed" references wherever you wish, instead of being > limited to the global level as with SET. For example: >
I'm a little puzzled by your remark. SET is not limited to binding in the global context. Rather, the target word is simply searched for up through the context hierarchy. The first match determines the context where the binding occurs, in exactly the same fashion as the look-up for the value of a word. For example: a: none ob: context [ b: none em-ob: context [ c: none set 'a 52 set 'b 53 set 'c 54 ] ] After this code is run, we have:
>> a
== 52
>> ob/b
== 53
>> ob/em-ob/c
== 54 We could also have said set [a b c] [52 53 54] in the em-ob, with exactly the same results. One limitation of SET is that it will only take a WORD or BLOCK as argument, it will not accept a path. In that sense your DO construct is more general. It is perhaps worth noting that in reading thousands of lines of "internal" port, view, vid, and desktop code, I don't recall ever seeing USE used, perhaps because it was broken (exported refs generated a GC crash)for most of the last 2 years. Clearly, it is not really needed to create complex REBOL programs. -Larry

 [10/13] from: joel:neely:fedex at: 12-May-2001 0:13


Larry Palmiter wrote:
> Hi Joel > > IMHO, using DO or USE is a more general mechanism, as it actually
<<quoted lines omitted: 4>>
> in the global context. Rather, the target word is simply searched > for up through the context hierarchy...
Excellent! Clearly I needed to do a little more REBOL nuclear physics. Thanks for the correction!
> One limitation of SET is that it will only take a WORD or BLOCK as > argument, it will not accept a path. In that sense your DO construct
<<quoted lines omitted: 3>>
> seeing USE used, perhaps because it was broken (exported refs > generated a GC crash)for most of the last 2 years.
I'm sincerely hoping that the "indefinite extent" bug is a thing of the past, although Ladislav's analysis of looped-use versus recursive-use makes me wonder if all the snakes have been chased out of the woodpile. AFAICT, saying ... use [foo baz quux ...] [ ; more activity here ] ... *should*be* equivalent to ... do func [/local foo baz quux ...] [ ; more activity here ] ... according to the available documentation. As described. However, we can test the looped-use versus recursive-use cases with this replacement and see a difference:
>> bb: []
== []
>> repeat i 3 [do func [/local x] [x: i append bb [x]]]
== [x x x]
>> bb
== [x x x]
>> reduce bb
== [1 2 3] Thus far, we have results consistent with the idea that USE (and the DO FUNC /LOCAL replacement) will create a new context with each evaluation. Now, for the recursive case:
>> ckscope: func [argvar cond /local locvar] [
[ locvar: argvar + 1 [ do func [/local privar] [ [ privar: locvar + 1 [ print [argvar cond locvar privar] [ if cond [ckscope argvar + 10 false] [ print [argvar cond locvar privar] [ ] [ ]
>> ckscope 1 true
1 true 2 3 11 false 12 13 11 false 12 13 1 true 2 3
>>
*THAT* result is consistent with dynamic scoping (e.g., in LISP) and with the expectations set up by the documents. Therefore, I'm swayed by this experiment (and by Ladislav's explanation) to regard the behavior of USE in this situation to be unfortunate at best and possibly buggy. Again, I'll have to say that I don't know whether this difference was intended by RT, or is a case that didn't get thought about, or is a case that was thought about but not viewed as having enough priority to be dealt with.
> Clearly, it is not really needed to create complex REBOL programs. >
The above rewrite demonstration clearly shows that we can get along quite nicely (more nicely, with present behavior) without USE in the dictionary. However, REBOL is riddled with examples of trivial bits of syntactical sugar that have even less explanatory value (IMHO). Consider: MAKE FUNCTION! vs FUNC vs FUNCTION vs DOES MAKE OBJECT! vs CONTEXT (a *very* unfortunate choice of words, in my humble opinion, considering that "context" is already a critical and not-well-explained and not-first-class REBOL concept) ...and I'm sure you could extend the list at least as quickly and as far as I could... The ability to state succinctly At this point I want to create a new scope for some lexically- local variables that will be invisible to the rest of the code. is a nice convenience. I've certainly seen my share of FUNCs that have a longer-than-convenient-to-remember list of local variables which are all declared in the argument block (to protect the global namespace) even if few of them are widely used throughout the body of the function. I really believe that being able to keep the scope of a variable as small as possible enhances readability and comprensibility. I'm just sad that the present implementation of USE raises as many questions as it answers. (And, as always, I'd love to be corrected if someone has a clearer mental model to offer.) -jn-

 [11/13] from: lmecir:mbox:vol:cz at: 12-May-2001 9:56


Yes, Larry,
> > I feel, that Joel tried to warn us, that the "Poor man's module" is not
as
> > reliable as some may need it to be: > >
<<quoted lines omitted: 24>>
> same way as a block of code in the global context. The only difference is > that those words which occurred in the spec-block as set-words are bound
to
> the objects context. Any code can appear and will be executed. > ]
your description is accurate. Just a little warning for the unaware: spec-block: [a: 11] spec-block-copy: copy spec-block ; now the contents of the Spec-block-copy is exactly the same as the contents of the Spec-block: all [ same? first spec-block first spec-block-copy same? second spec-block second spec-block-copy ] ; == true o: make object! spec-block ; now the contents of the Spec-block-copy is not the same as the contents of the Spec-block: all [ same? first spec-block first spec-block-copy same? second spec-block second spec-block-copy ] ; == false ; this example illustrates, that the Make Object! function modifies its Spec-block argument, ; although the change is invisible for the naked eye, because: all [ equal? first spec-block first spec-block-copy equal? second spec-block second spec-block-copy ] ; == true The same property holds for the Use function too.
> UNPROTECTED1 is not a first-level set-word, so it is not in the objects > context and thus no protection of global namespace is implied.
<<quoted lines omitted: 4>>
> want UNPROTECTED1 to not affect the global context, we just add > UNPROTECTED1: none in our object, making it a first-level word.
Correct, we do have a pretty good control over our contexts. What I wanted to underline is, that we are not protected against our own omissions.
> REBOL currently is based on a context hierarchy, where nested funcs and
objs
> have access to words in all the higher levels of the hierarchy, as well as > those in their own context.
The last statement is very questionable to me. I wrote http://www.sweb.cz/LMecir/contexts.html to prove, that there is only a Virtual Context Hierarchy in Rebol, i.e. Rebol contexts aren't hierarchical at all. Regards Ladislav

 [12/13] from: lmecir:mbox:vol:cz at: 12-May-2001 10:05


Hi, Larry wrote:
> Hi Joel > > IMHO, using DO or USE is a more general mechanism, as it actually
<<quoted lines omitted: 5>>
> the context hierarchy. The first match determines the context where the > binding occurs, in exactly the same fashion as the look-up for the value
of
> a word. For example:
I would like to change the above wording starting "Rather ...": Rather the targer word is simply examined for its binding (the context it belongs to is fetched).

 [13/13] from: coussement:c:itc:mil:be at: 15-May-2001 9:51


Hi REBOLians: I would like to express my gratitude to all those who have contributed to this thread. I didn't expect so much enthousiasm and I'm glad I could stimulate your intellect ;-)) I will take some time before I've read -and understand- all those 40+ pages of mail, but I'm sure I will -again- learn a lot, which is always a pleasure in REBOL :-) A special thanks to Joel Neely, which initiate the answers to the thread ! Be the REBOL with you ! chr==

Notes
  • Quoted lines have been omitted from some messages.
    View the message alone to see the lines that have been omitted