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