This tripped me up (again) today
-
For further reading, see this topic on References:
By value or by reference? -
This is interesting, in Ruby 1.9.x Core Docs, there is a method called:
Array.dcloneThe source shows that it creates a totally new array, including new cloned elements, and handles nested arrays. (It's a recursive method.)
# File lib/rexml/xpath_parser.rb, line 22 def dclone klone = self.clone klone.clear self.each{|v| klone << v.dclone} klone end
Looks like it's an Array class extension defined in the REXML extended Library.
-
Are
.dup
and.clone
aliases? -
I think so.
-
-
FWIW This whole area of Comp Sci is pretty tricky to resolve in programming languages generally because ultimately these references are not 'semantic free'.
Sometimes you want a 'deep' copy and sometimes you want a 'shallow' copy. The classic example is if you have a class Car which has an attribute which is a reference to the car manufacturer, when I copy the car, I generally wouldn't want to deep copy the entire Ford motor company (or whatever).So languages essentially cannot a priori "know" what the meaning and therefore the intent of these references are, hence we have to either manually descend a parts hierarchy choosing to copy or not copy, or keep a top-level reference.
Adam
-
It might be eaier to explain by looking at object id's:
>> a = %w( one two three ) => ["one", "two", "three"] >> b = a => ["one", "two", "three"] >> a.object_id => 23021928 >> b.object_id => 23021928
Here is an image what what this means in the computer. Both a and b now reference the same object, an Array. However, note that each array element is a reference to another object.
When we
.dup
a, we get a new Array object, including it's elements. But remember the elements are just references to the original objects.>> a = %w( one two three ) => ["one", "two", "three"] >> b = a.dup => ["one", "two", "three"] >> a.object_id => 23021928 >> b.object_id => 22854408
Which can be seen by:
a[0].object_id == b[0].object_id => true
So when you .dup an array, you get a new array. Because the elements of the original array are references to a non-primitive type (String in this case,) the new array's elements reference the original elements.
As Dan says, there are no variables in Ruby, only references to object.
Here's someone else's explaination
@unknownuser said:
When you copy an object, all you do is copy its set of instance variables, which are just references to other objects. For an array, the instance variables are its set of indexes, which again are just references. Copying an array just means making a new list of references, but the objects they point to remain unmodified and uncopied.
-
@thomthom said:
Are
.dup
and.clone
aliases?NO, even though both create "shallow" copies:
- .clone copies the frozen and tainted state of the receiver object.
- .dup copies the tainted state of the receiver object.
In addition:
@unknownuser said:
(http://phrogz.net/ProgrammingRuby/ref_c_object.html#Object.dup)":1q9fng7v]In general, clone and dup may have different semantics in descendent classes. While clone is used to duplicate an object, including its internal state, dup typically uses the class of the descendent object to create the new instance.
In general it is always safer to try using dup first (especially if you don't want to get a frozen object.)
@unknownuser said:
(http://forums.sketchucation.com/viewtopic.php?f)":1q9fng7v]
@kwalkerman said:
So apparently, you can do face.clone, ... I if so, I might be able to determine what is happening with face.clone
Karen,
The .clone and .dup methods come from standard Ruby class Object, and create "shallow" copies. Ie, (from the book,) "the instance variables of the obj are copied, but not the objects they reference."
FYI: I went on to say not to use these methods with Sketchup's C++ objects (... until they get overriden methods that work correctly.)
-
I use clone a lot for SketchUp's objects...
What is the issue with SU's.clone
? -
@thomthom said:
I use clone a lot for SketchUp's objects...
What is the issue with SU's.clone
?It WILL work for any classes that are subclasses of standard Ruby classes, such as Length, which is a subclass of Float.
It WILL work for any custom Sketchup classes that provide an overidden version of clone, such as:
Geom::Point3d
Geom::Transformation
Geom::Vector3d- note that these work like dup and not clone (in that they do not copy the frozen state of the receiver.)
The Ruby inherited edition will not work for many C++ objects like Sketchup::Face.
To get around this, TIG shows in the other thread, how to use Sketchup::Group.copy to "clone" Sketchup::Drawingelement subclass objects, like Faces, Edges, etc.
Geom::BoundingBox objects can be copied by using their .add method (if clone or dup does not work):
bb2 = Geom::BoundingBox.new.add( bb1 )
-
So SU doesn't override .dup for Point3d and Vector3d?
That would explain why I had problems before when I tried .dup for Point3d. I've had to use .clone. -
@thomthom said:
So SU doesn't override .dup for Point3d and Vector3d?
That would explain why I had problems before when I tried .dup for Point3d. I've had to use .clone.Yepper... I remember having that discussion with you in another topic thread.
Either most the Sketchup classes need to override Ruby's .dup and .clone with methods that work, or they should be removed for those classes.
And... a point about .freeze, we would not want to freeze most of the Ruby objects that Sketchup needs to modify (data classes and any object class that is kept in the model.) So copying the frozen state doesn't mean much, so the overriden dup could likely be just an alias for the overriden clone.
In most Sketchup classes the .freeze method should be also removed. It can be done for many of them, by removing it from Sketchup::Entity.
-
I passing... IF you want to make a completely separate array based on another array's 'reference' then us
[]+
so
a=[1,2] b=[]+a
makes arraya
and arrayb
separate arrays as
a[0]=99
gives
a ==> [99,2]
butb
is not affected
b ==> [1,2]
? -
I am SO glad that I found this 5-year-old thread. I, too, was having trouble copying an array and having the copy be independent of the original.
In my case, I had an array of points that I had generated, and I wanted to create a closed curve from them. My strategy was to copy the point array
curve = []
curve = point
and then add point[0] to the end,
curve << point[0]
so that a curve created from the new curve[] array would return to its beginning point.
path = ents.add_curve curveBut the above sequence kept adding the extra point to the point array too.
What I finally kludged around to was
curve = []
curve = point[0..(point.length - 1)]
curve << point[0]
path = ents.add_curve curveUsing the sample code from the above discussion, I have tested and confirmed that
b = a.dup
and
b = a.clone
both have the same problem as my initial
curve = pointAnd I have confirmed that
b = a.dclone
gives an error.
I'm using SU 15, which is supposed to have Ruby 2.0 and Dan notes that dclone is in 1.9, so I'm confused.I do have a kludge that works. Maybe it's not so much of a kludge after all. I understand the issue a little more, and hopefully I will remember it in the future.
FWIW, I hope this helps,
August -
The assignment
curve = point
means the array 'curve' refers to the array 'point' - they are essentially referencing the same thing, whereas...
curve = point.clone
means the array 'curve' is a separate array, which has been made as a copy from the array 'point',
Consider this...
curve = point + [ point[0] ]
which achieves you aim for a 'curve' array defining a 'loop', but done in the one step.
It combines the array 'point' and a new array made from the first element of that array, all in a new array named 'curve'.Incidentally consider naming arrays and other collections in the plural - it is is easy to follow the code - so the array named 'points' consists of a collection of elements, each of which is a 'point'.
The 'curve' array would also perhaps be better named 'curve_points' -
Thanks TIG.
I had not tried using that additional set of square brackets. It makes sense.
As for naming, I thought about using plural in the first place, but most of my usages were as singular references, point[0], point[1], ... point[n-1], point[n] where the singular read better to me. I like "curve_points" -- that is always used as a group, never individually, so plural reads much better there.
Thanks,
August -
TIG, I'm not sure that clone does what you say it does.
This page, http://lukaszwrobel.pl/blog/copy-object-in-ruby suggests that a clone's elements still point to the original elements so some changes to one will indeed show up in the other. That's what I found with my initial testing.
Above, Dan says
@unknownuser said:It WILL work for any custom Sketchup classes that provide an overidden version of clone, such as:
Geom::Point3d
Geom::Transformation
Geom::Vector3d- note that these work like dup and not clone (in that they do not copy the frozen state of the receiver.)
The Ruby inherited edition will not work for many C++ objects like Sketchup::Face.
So I didn't use .clone.And yet, I just tried in the SU Ruby console:
> a = ["a","b", "c"] ["a", "b", "c"] > b = a ["a", "b", "c"] > c = a.clone ["a", "b", "c"] > d = a << "d" ["a", "b", "c", "d"] > a ["a", "b", "c", "d"] > b ["a", "b", "c", "d"] > c ["a", "b", "c"] > a[2] = "3" 3 > a ["a", "b", "3", "d"] > b ["a", "b", "3", "d"] > c ["a", "b", "c"] > d ["a", "b", "3", "d"]
which shows that for these two kinds of changes, the clone is not affected.
I'm still not clear on when I can use .clone and when not, nor do I really understand shallow vs. deep copies and frozen objects, so for now, unless I'm doing tens of thousands of copies, I may use a brute force method becuase the shortcuts seem so problematical.
Thanks,
=A= -
@august said:
I'm using SU 15, which is supposed to have Ruby 2.0 and Dan notes that
dclone
is in 1.9, so I'm confused.What I actually said was that the
REXML
library (beginning in Ruby 1.9,) modified theArray
class, by adding thedclone
method.In order to use it, you must either precede it's use with:
require "rexml/document"
or use a refinement.(1) Using the
REXML
library:
` require "rexml/document"true
a = []
[]
a.respond_to?(:dclone)
true`(2) Refinement module:
<span class="syntaxdefault"><br />module August<br /> module DeepCopy<br /><br /> refine </span><span class="syntaxkeyword">Array do<br /> </span><span class="syntaxdefault">def dclone<br /> klone </span><span class="syntaxkeyword">= </span><span class="syntaxdefault">self</span><span class="syntaxkeyword">.clone<br /> </span><span class="syntaxdefault">klone</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">clear<br /> self</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">each</span><span class="syntaxkeyword">{|</span><span class="syntaxdefault">v</span><span class="syntaxkeyword">| </span><span class="syntaxdefault">klone </span><span class="syntaxkeyword"><< </span><span class="syntaxdefault">v</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">dclone</span><span class="syntaxkeyword">}<br /> </span><span class="syntaxdefault">klone<br /> end<br /> end </span><span class="syntaxcomment"># class Array<br /><br /> </span><span class="syntaxdefault">end </span><span class="syntaxcomment"># refinement module DeepCopy<br /></span><span class="syntaxdefault">end </span><span class="syntaxcomment"># Author's namespace<br /><br /><br /></span><span class="syntaxdefault">using August</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">DeepCopy<br /><br />module August<br /> module SomePlugin<br /> <br /> a </span><span class="syntaxkeyword">= [</span><span class="syntaxstring">"august"</span><span class="syntaxkeyword">,</span><span class="syntaxstring">"dan"</span><span class="syntaxkeyword">,</span><span class="syntaxstring">"tig"</span><span class="syntaxkeyword">]<br /> </span><span class="syntaxdefault">b </span><span class="syntaxkeyword">= </span><span class="syntaxdefault">a</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">dclone<br /> <br /> puts </span><span class="syntaxstring">"a is; #{a.inspect}"<br /> </span><span class="syntaxdefault">puts </span><span class="syntaxstring">"b is; #{b.inspect}"<br /> </span><span class="syntaxdefault">puts </span><span class="syntaxstring">"changing a[1] to \"bob\""<br /> </span><span class="syntaxdefault">a</span><span class="syntaxkeyword">[</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">]= </span><span class="syntaxstring">"bob"<br /> </span><span class="syntaxdefault">puts </span><span class="syntaxstring">"a is; #{a.inspect}"<br /> </span><span class="syntaxdefault">puts </span><span class="syntaxstring">"b is; #{b.inspect}"<br /><br /> </span><span class="syntaxdefault">end </span><span class="syntaxcomment"># module SomePlugin<br /></span><span class="syntaxdefault">end </span><span class="syntaxcomment"># Author's namespace<br /> </span><span class="syntaxdefault"></span>
Any call to
using()
must occur within theTOPLEVEL_BINDING
.
This restriction has been removed in later versions of Ruby 2.2+, and the experimental warning that is output on calls torefine
has also been removed. (Ie, refinements are no longer experimental and calls tousing
can happen inside specificmodule
andclass
scopes.)If you want to see that all the elements of the arrays are different objects, you can iterate them and compare their object id numbers.
-
Thanks Dan,
So refine allows me to add my own operators to an existing class? That's a sweet (and dangerous) concept.
I'll have to put that on the back burner for now. I'm still working on basic Ruby.
-
@august said:
So refine allows me to add my own operators to an existing class? That's a sweet (and dangerous) concept.
Refinements do not affect other people's scripts that do not "use" the refinement module. (They are only valid within the file that has the
using
call.)
Advertisement