[REBOL] Re: Obscure? You be the judge!
From: joel:neely:fedex at: 15-May-2002 8:32
Hello, all,
(I'm replying to self because several folks have made helpful
comments during this thread. Thanks to all who've contributed!)
I've done further experimenting to try to clarify the differences
for myself. Perhaps they will be of help to someone else. My
apologies for the length, but I'd appreciate comments on the
coding convention at the end.
-jn-
Joel Neely wrote:
> Thanks to your explanation, I have a more compact example...
>
Thanks to comments from Volker, Gabriele, and Ladislav, I have
a more complete example, given below. I'm sure that all I've
done here is re-discover/-state what was already said, but I'm
a bit slow... ;-)
gub?: make object! [
data: [1]
proto: make object! [
number: 0
dataref: data
speak: func [] [print [number mold dataref]]
]
things: []
append things make proto [number: 1]
append things make proto [number: 2 dataref: data]
append things make proto [number: 3 dataref: proto/dataref]
tweak-n-speak: func [blk [block!]] [
insert data blk
proto/speak
foreach thing things [thing/speak]
]
]
By adding the line containing "PROTO/SPEAK" to the last function,
I was able to get more photons from the filament!
>> gub?/tweak-n-speak ["what's" "different?"]
0 ["what's" "different?" 1]
1 [1]
2 ["what's" "different?" 1]
3 ["what's" "different?" 1]
When GUB?/PROTO was made, its DATAREF was initialized to refer to
GUB?/DATA as expected.
When GUB?/PROTO/1 was made, it began life as a (deep) clone of
GUB?/PROTO and then had its NUMBER set to 1. Its DATAREF now
refers to a clone of the initial GUB?/DATA (via the reference in
GUB?/PROTO/DATAREF).
When GUB?/PROTO/2 was made, it began life as a (deep) clone of
GUB?/PROTO and then had its NUMBER set to 2, *and* its DATAREF
(re-)set to refer to the same series as GUB?/DATA (explicitly,
via the GUB?/DATA reference).
When GUB?/PROTO/3 was made, it began life as a (deep) clone of
GUB?/PROTO and then had its NUMBER set to 3, *and* its DATAREF
(re-)set to refer to the same series as GUB?/DATA (explicitly,
via the GUB?/PROTO/DATAREF reference).
At that point the references in GUB?/DATA and GUB?/PROTO/DATAREF
and GUB?/THINGS/2/DATAREF and GUB?/THINGS/3/DATAREF are all
equivalent, while the reference in GUB?/THINGS/1/DATAREF refers
to a distinct block (originally cloned via GUB?/PROTO/DATAREF).
Normal shared-block-reference behavior ensues *after* this point.
KEY LESSON: When using an existing object as a prototype, the
default behavior is to copy referenced *values*,
and special effort must be taken to *suppress* the copying, in
contrast to other REBOL handling of reference data, where the
default behavior is to copy *references* and special effort
must be taken to *cause* copying.
For example, every REBOL programmer eventually learns that:
palindrome: func [b [block!] /local result] [
result: []
append result b
append result head reverse b
result
]
will (probably) surprise him/her *twice*, as in:
>> foo: [1 3 5 7]
== [1 3 5 7]
>> baz: palindrome foo
== [1 3 5 7 7 5 3 1]
>> foo
== [7 5 3 1]
>> bletch: palindrome [2 4 6]
== [1 3 5 7 7 5 3 1 2 4 6 6 4 2]
and therefore should (probably) be written more like:
safe-palindrome: func [b [block!] /local result] [
result: copy []
append result b
append result head reverse copy b
result
]
(Yes, I know this dinky example can be written *much* better;
please don't miss the point and try to optimize this specimen
of noncopy-vs-noncopy behavior. If I had rewritten it as
safe-and-short-palindrome: func [b [block!]] [
join b head reverse copy b
]
the point regarding copying would have been harder to see.)
In contrast to the you-must-ask-for-copying-if-you-want-it
general rule, when creating objects from a prototype object
that is intended to contain reference(s) to shared block(s),
REBOL requires that we take special action to *avoid* the
default copying behavior.
It seems to me that the clearest way to do this is likely
something similar to what I did above with the third anonymous
object in GUB?/THINGS where the prototype contains:
proto: make object! [
number: 0
dataref: data
;;...
]
and the construction-from-prototype pattern of
make proto [
number: 3
dataref: proto/dataref
]
explicitly calls the reader's attention to the fact that the
DATAREF attribute of the new object is intended to share
reference with the corresponsing attribute of the prototype.
(In passing, it's interesting that NUMBER must be initialized
explicitly to get a different value from the prototype, but
DATAREF must be initialized explicitly to get the same value
as the protype.)
I'm leaning toward writing
dataref: proto/dataref
instead of
dataref: data
in the construction-from-prototype expression to make it clear
that the *current* value of the prototype's attribute should be
the basis of the shared reference. For example...
some-proto: make object! [
block-ref: some-global-block
;;...
]
other-proto: make first-proto [
block-ref: other-global-block
;;...
]
leaves a situation in which
some-instance: make some-proto [
block-ref: some-proto/block-ref
;;...
]
other-instance: make other-proto [
block-ref: other-proto/block-ref
;;...
]
is more likely correct (and obvious to the reader) than
some-instance: make some-proto [
block-ref: some-global-block
;;...
]
other-instance: make other-proto [
block-ref: some-global-block
;;...
]
Thoughts?
--
; Joel Neely joeldotneelyatfedexdotcom
REBOL [] do [ do func [s] [ foreach [a b] s [prin b] ] sort/skip
do function [s] [t] [ t: "" foreach [a b] s [repend t [b a]] t ] {
| e s m!zauafBpcvekexEohthjJakwLrngohOqrlryRnsctdtiub} 2 ]