REBOL 3.0

Comments on: Objects - to copy or not to copy?

Carl Sassenrath, CTO
REBOL Technologies
22-Aug-2009 15:58 GMT

Article #0239
Main page || Index || Prior Article [0238] || Next Article [0240] || 15 Comments || Send feedback

Updated

This article has been updated and sorry that I am repeating the same issues as discussed in #212. Sometimes, it seems that the "continuation" stack is not large enough. Anyway, a more specific article is being prepared and will be posted soon.

In R3, object creation from a parent object will deep copy internal strings, blocks, and objects, but not images and vectors.

There are advantages and disadvantages to this policy. By deep copying, you don't need to worry about conflicts in sharing. Each object has its own copies. But, the cost is memory. If your object contains a 10K string, every instance of it will add another 10K.

Maxim's recent posting on CureCode about this topic has got me thinking that we don't want to deep copy by default. This allows developers to be smart about their code. They don't have to chew up memory if they don't want to.

This issue is fairly major, and we need to finalize it soon in order for R3 to go beta. Not much code is written for R3 yet, so we won't break too much (mainly the GUI code - depends heavily on objects.)

And, it would be possible to write...

obj: make copy/deep parent [...]

for those that want it. (That code deep copies the parent object values before creating the new object.)

15 Comments

Comments:

Maxim Olivier-Adlhoch
22-Aug-2009 12:25:45
Thanks for addressing this :-)

Do we really need the make in the example above?

I was under the assumption that copy, as-is, already created a new object, if the parent is an object.

For me the issue is also about references. if you copy the internals, you lose all references to shared data, and its something we can`t reverse afterward, since you can`t easily deduce if the resulting string comes from the original, or some clever init spec.

Brian Hawley
22-Aug-2009 15:33:25
The results of the discussion the last time the question was asked (blog #212):
  • MAKE object! - Fields with any-word!, any-block!, function! or closure! values would be BIND/copy'd, values of all other types referenced.
  • COPY object! - Values of all fields referenced.
  • COPY/deep object! - Values of all fields deep-copied.

The protection status wasn't addressed in that discussion; my preference would be for hidden fields (PROTECT/hide) to not be copied, and for protected fields (PROTECT) to not be protected in the new object.

This gives us a sensible default (MAKE), and more flexible options if we need them. We can use the MAKE spec or wrapper code to provide more options.

Giuseppe Chillemi
23-Aug-2009 4:30:15
I prefer data sharing between objects by default and copying only when the programmer requires it.

Sunanda
23-Aug-2009 11:35:05
I've looked at several large applications written in R2. They all, at some point, need objects _not_ to be copied when fresh instances are created.

Basically, I prefer R2's way of creating objects:

template: make object! [
         obj: make object! [data: random/secure now/time/precise]
         fun: does random/secure ['a 'b 'c 'd 'e 'f 'g]
         str: random/secure "abcdefghjiklmnopqrsutvwxyz"
         blk: random/secure [a b c d e f g h i]
         iss: random/secure #1234567890
         fil: random/secure %a1b2c3d4e5
         num: random/secure 99999999999
         dat: random/secure now
         ]
         
parent: make template []
child: make parent []

;;R2: foreach word next first parent [ print [word "same:" same? a: get in parent word b: get in child word "equal:" a = b] ] ;;r3: foreach word words-of parent [ print [word "same:" same? a: get in parent word b: get in child word "equal:" a = b] ]

R2 result:

obj same: true equal: true
fun same: false equal: true
str same: false equal: true
blk same: false equal: true
iss same: false equal: true
fil same: false equal: true
num same: true equal: true
dat same: true equal: true

R3 result:

obj same: false equal: true
fun same: false equal: true
str same: false equal: true
blk same: false equal: true
iss same: false equal: true
fil same: false equal: true
num same: true equal: true
dat same: true equal: true
Henrik
23-Aug-2009 14:33:52
I agree that it should not deep copy by default, but it should be easy to deep copy the object as a separate operation.
Maxim Olivier-Adlhoch
23-Aug-2009 15:00:07
might I propose a simple method to override any make defaults?

Similar to my last proposal, but simplified.

first, 'MAKE simply checks to see if the parent object has some function called '--INIT-- (or any name which is decided upon by common accord).

If so, it creates the new object as a fully referenced copy & applies the spec block, if any, as usual.

Then 'MAKE automatically calls the '--INIT-- function within the new object, with the master object as first argument.

That function can now do what it wants with the new object, which is returned, as usual, by 'MAKE. Things like checking difference between new and master, explicitly copying specific values like strings used for field storage, etc.

This would also replace the INIT block of VID found in R2, and would provide a generalized and common initialization system which many of us have had to improvise anyways.

Some objects need to be initialized with more complex decisions, and having it within the master means you don't have to add tracking of base type, and match it with unbound spec blocks, which are pretty complex to manipulate for beginners (and add lots of overhead, even for the experienced).

This function would *NOT* be automatically called by 'COPY, which also adds useful differentiation between 'MAKE and 'COPY.

As an option, this could be made as mezz like so:

NEW: func [master [object!] spec [block!] /local obj init][
    ; note: I assume copy rebinds funcs to new obj.
    obj: copy master
    bind/new spec: copy spec obj
    do spec
    if action? init: get in obj '--init-- [
        init obj master
    ]
]
I guess the advantage of having this within 'MAKE directly is a simple case of speed, I can see most PITL apps using it, so it could make a difference in larger apps.
Brian Hawley
23-Aug-2009 15:49:45
Maxim, please no. MAKE object! already has an init function: the spec block. It doesn't need another.

We could get the same effect by ensuring that spec blocks can be reused. I'm not saying that they can't be reused now; I'm saying that if we state that a requirement of MAKE object! is that the spec block must be reusable to make other objects (however that is accomplished), then we won't need to add an init function, ever.

Christian Ensel
23-Aug-2009 16:08:13
The difference between copying and deep copying looks very fundamental to me, probably fundamental enough not be buried in refinements.

So how's about having

CLONE object! - Values of all fields referenced

vs.

COPY object! - Values of all fields (deep-)copied

with CLONE returning a flat copy and COPY returning a deep copy of the object?

I'm not good at foreseeing the consequences, but to me it seems to emphasize the difference quite nicely. Modifying a mutable value in an object affects this object's clone, it doesn't affect copies of that object.

It has the further advantage of avoiding the refinement check when you're just cloning. The costs of an additional /deep refinement check are minimal when compared to the costs of deep copying, but when you are cloning objects you're probably doing so because you are concerned about speed and/or memory. Hence, economizing on that additional check may be worth having two different words for very different outcomes.

Maxim Olivier-Adlhoch
23-Aug-2009 21:13:03
Brian, you don`t get the fact that a spec block is outside the object. I have an application which manages more than 200 different objects of different type, levels and class, yet all live in the same space.

having to keep tabs on how different stuff gets initialized is extremely complex when its external.

spec blocks are for instances, I`m talking about recurring class-wide initialization, like automatically assigning a serial id to all objects, independently of the fact that an instance has any kind of specific stuff to set (like a name or a label).

looking around in other people`s code I see I`m not the only one which eventually has to manage this ugly fact of large application dev.

what I`m asking is for an RT standard way of dealing with this issue, which IS NOT handled by spec blocks.

having a standard mezz like I propose is plenty, at least there will be a standard way to provide minimal class initialization (even if we are dealing with prototypes).

ports have an internal init (and more) cause they have to do stuff when created, independently of the user data it needs submitted... what I`m asking is to provide this same (and laking) functionality to objects too.

Anton Rolls
27-Aug-2009 9:52:42
I think the example was supposed to be:

obj: make parent copy/deep [...]

using COPY/DEEP to avoid the sharing that MAKE does.

Carl Sassenrath
27-Aug-2009 20:33:41
Actually, it is correct as written. That is, it copies all the series of the parent.

I am preparing a more detailed analysis of the situation, including a proposed design. I will post it as soon as it's ready.

Nick
28-Aug-2009 1:51:06
How about make and make/deep?
John Niclasen
28-Aug-2009 3:26:09
"This is different from R2 which only deep copied functions (for rebinding their variables.)"

Didn't R2 deep copy everything except objects, when creating new objects?

Brian Hawley
28-Aug-2009 11:02:32
R2 did a BIND/copy, not a COPY/deep. This meant that series that weren't bindable weren't copied, including string!, binary!, list! and hash!. Word values weren't rebound either (for some reason?):
>> o: context [a: 1 b: 'a]
>> o2: make o [a: 2]
>> get o2/b
== 1

In my proposal above I suggested that word values be rebound, but I'm not sure that is a good idea, and R3's SELECT makes it less necessary. If you keep the binding you can bind class names to their containing context, and so access them quicker. However, you can access them with SELECT regardless of their binding, and also in map! containers.

Carl Sassenrath
28-Aug-2009 12:52:39
John, yes, there were some incorrect statements, and I've updated the article.

I will be posting a new article with more specific information.

Post a Comment:

You can post a comment here. Keep it on-topic.

Name:

Blog id:

R3-0239


Comment:


 Note: HTML tags allowed for: b i u li ol ul font span div a p br pre tt blockquote
 
 

This is a technical blog related to the above topic. We reserve the right to remove comments that are off-topic, irrelevant links, advertisements, spams, personal attacks, politics, religion, etc.

REBOL 3.0
Updated 17-Oct-2017 - Edit - Copyright REBOL Technologies - REBOL.net