[REBOL] Re: More on "embedded" objects (an enhancement)
From: joel::neely::fedex::com at: 16-May-2001 8:06
Hello, self and list, ;-)
As promised from last night... (The coding was trivial, but I
wanted time to run a bunch of test cases and write a better
description before posting.)
Joel Neely wrote:
> A possible future enhancement (which it's too late for me
> to tackle tonight) would be to add another refinement to
> allow sharable attribute creation when instantiating a new
> object from an existing object. I may take a quick look at
> that in the morning.
>
The enhanced version with a /SHARED refinement appears below,
followed by new and improved (I hope) documentation.
8<----------------------------------------------------------
instantiate: func [
[catch]
spec1 [block! object!]
/unique spec2 [block!]
/shared spec3 [block!]
/local ospec uspec uattr result
][
ospec: copy []
uspec: copy []
uattr: to-word "; unique"
if all [object? spec1 found? in spec1 uattr] [
append uspec get in spec1 uattr
]
if unique [
append uspec spec2
]
if 0 < length? ospec: copy/deep uspec [
append ospec reduce [to-set-word uattr 0]
]
if shared [
append ospec spec3
]
result: either object? spec1 [
make spec1 ospec
][
make object! append copy/deep spec1 ospec
]
if 0 < length? uspec [
set in result uattr uspec
]
result
]
8<----------------------------------------------------------
INSTANTIATE constructs objects.
In the simplest case (i.e. no refinements -- ever!) it is
almost the same as MAKE OBJECT! with the following exceptions:
new-object: instantiate spec-block
creates an object from the supplied block, but words in that
block are NOT bound to the new objects context. In addition
new-object: instantiate spec-object
creates a new object from the supplied object, without the
need for an empty block when no modifications are desired.
The /UNIQUE refinement requires an additional block, which
is combined with the original spec (block or object) to
override and/or extend attributes for the new object, just
as
new-object: make spec-object extra-block
can incorporate changes/additions to attributes. However,
there's another difference. When creating an object via
new-object: instantiate/unique spec-thing u-block
(where SPEC-THING can be either a block or another object)
the new object will distinguish between its "shared" parts
and its "unique" parts. If yet another object is created
later using
newer-object: instantiate new-object
the *current* state of the shared parts will be used as
the basis of the newer object, but the *original* unique
spec will be used to create a fresh copy of all unique parts.
To illustrate:
o1: instantiate/unique [
a: 1
][
b: 10
gimme: func [] [print ["I contain" a "and" b]]
]
creates an object with a shared A and unique B, with the
following behavior
>> o1/gimme
I contain 1 and 10
>> o1/a: 2
== 2
>> o1/b: 20
== 20
>> o1/gimme
I contain 2 and 20
Using O1 as the basis for a later object give the following
results,
>> o2: instantiate o1
>> o2/gimme
I contain 2 and 10
where the unique attribute O2/B is initialized per the
original unique spec block, rather than coming from the
current state in O1.
The /SHARED refinement allows an additional spec block to
be supplied to override/extend the original specification,
but *without* marking its content as unique. Therefore,
>> o3: instantiate/shared o2 [a: 100]
>> o3/gimme
I contain 100 and 10
overrides the current O2/A, but that change is not marked
as unique. As the following transcript shows
>> o3/a: 200
== 200
>> o3/b: 30
== 30
>> o3/gimme
I contain 200 and 30
>> o4: instantiate o3
>> o4/gimme
I contain 200 and 10
the A attribute still comes from the current spec object,
while future B attributes still revert to the original
specification.
The /SHARED refinement is allowed when building from a
spec block as well. It will extend/override the base spec
block, which creates "shared" attributes by default.
Finally, both the /UNIQUE and /SHARED refinements are
cumulative in effect:
>> o: instantiate/unique [
[ a: 1
[ ][
[ b: 10
[ sum1: func [][a + b]
[ ]
>> o/a: 2
== 2
>> o/b: 20
== 20
>> o2: instantiate/shared o [
[ c: 100
[ sum2: func [][a + b + c]
[ ]
>> o3: instantiate/unique o2 [
[ d: 1000
[ sum3: func [][a + b + c + d]
[ ]
so that A and C are shared, while B and D are unique
>> o3/a: 3
== 3
>> o3/b: 30
== 30
>> o3/c: 300
== 300
>> o3/d: 3000
== 3000
>> o4: instantiate o3
>> o4/sum3
== 1313
Finally, of course (at last!), the most subtle and critical
issue with /SHARED (or default) versus /UNIQUE is this:
since the cumulative unique specifications in a prototype
object are re-evaluated from a clean copy whenever that
prototype object is INSTANTIATE-d,
1) any UNIQUE initialization activity will be re-evaluated
in the combined context of the new object and the global
context, and
2) any UNIQUE-ly "embedded" sub-objects will be re-created
in the new context (1), while SHARED "embedded" sub-
objects will simply refer back to the sub-object in the
prototype object. Enclosing-object words in an embedded
sub-object will refer to the original enclosure for
SHARED (or default) sub-objects but will refer to the
current enclosure for UNIQUE sub-objects.
Hope this helps! (And if you read this far, I owe you a
virtual cookie for your patience and endurance!)
-jn-
--
------------------------------------------------------------
Programming languages: compact, powerful, simple ...
Pick any two!
joel'dot'neely'at'fedex'dot'com