Ruby: shallow copy surprise!
thingsaaronmade.com
thingsaaronmade.com
>> x = [1,2,3]
=> [1, 2, 3]
>> def go(z); z[1] = 0; end
=> nil
>> go(x)
=> 0
>> x
=> [1, 0, 3]
Mutable objects in Ruby are passed by reference, not value.Greg Brown was kind enough to steer me onto the right track though, this is mainly a design problem. I should be avoiding copying wherever possible.
The problem with always defaulting to deep copy in a language where all object slots are by reference is "where do you stop?" Do you copy all the objects known to that object? If the object holds a data file or a resource, do you deep copy that too? What about object graphs? What about two objects mutually holding references to each other? What if the object holds a reference to the global application object?
So the most common way to do it is default to only a shallow copy. It's up to the user to define a deep copy if they need it because only the user knows what members are semantically "part of" the parent object and what are "pointers" to unowned objects.
I would be very interested in a cleaner variant on the marshal load hack for non-primitives, or even some interesting doc/writeup on how this works.
edit: I thought it was not uncommon for higher level languages to pass arrays and objects by reference, so this post wasn't particularly new or interesting. Unless you're coming from PHP, which is, IMO, a nightmare because everything is passed by value (by default) except objects.
Is there something I'm missing here? Some idiom that lets you side-step this problem? I'm questioning it because this problem/solution seems very much at-odds with the elegance and thoroughness of the rest of the language.
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.
Consider:
<pre> array = ["foo"] copy = array.dup </pre>
array and copy are independently mutable - modifying the index in one does not affect the indexes in the other - but they still both contain references to the single string "foo". Thus:
<pre> copy.first.gsub! /foo/, "bar" </pre>
modifies the string referenced by copy, which is the same string referenced by array. So array becomes ["bar"].
If you want a true deep copy, do something like this:
<pre> def deep_copy(object) case object when Array object.map { |item| deep_copy(item) } when Hash object.inject({}) do |hash, (key,value)| hash[deep_copy(key)] = deep_copy(value) hash end # handle other data structures if need be else object.respond_to?(:dup) ? object.dup : object end end </pre>
One thing that confused the issue a little for me is the fact that some objects in Ruby are actually only really 'pretend objects'. ie:
>> test = 4
=> 4
>> test2 = 4
=> 4
>> test.object_id
=> 9
>> test2.object_id
=> 9
I don't know enough about the deeper parts of the language to know what else there is that's like this though... >> ((1 << 30) - 1).class
=> Fixnum
>> ((1 << 30)).class
=> Bignum
>> ((1 << 30) - 1).object_id
=> 2147483647
>> ((1 << 30) - 1).object_id
=> 2147483647
>> (1<<30).object_id
=> 166070
>> (1<<30).object_id
=> 161200http://ruby-doc.org/core/classes/Object.html#M000351 http://ruby-doc.org/core/classes/Object.html#M000352
>> a = b = [1,2]
=> [1, 2]
>> c = a.dup
=> [1, 2]
>> a==b
=> true
>> a==c
=> true
>> a.equal?(b)
=> true
>> a.equal?(c)
=> false
Here's a better post on the topic:
http://kentreis.wordpress.com/2007/02/08/identity-and-equali...It's not necessarily obvious if you're coming from other languages that don't behave this way. That being said I'm surprised that I had never run into this problem before. I think that most of the time I had the right idea with not copying objects, but in this case I had memoized a method call and the Hash 'cache' was getting corrupted which was what brought it to my attention... A slightly more unusual situation.
This means if you have classes returning Strings, such as first_name, last_name, address etc, your getter should return a dup() if you want to ensure no accidental change to it. That sucks, if you ask me.
Lots of methods are non-destructive and it's cleaner to use them instead of artificially calling dup().
The API returns strings to you, the user at some point needs to (say) perform multiple operations on that String. Say, multiple gsubs. So rather than create a new string with each, he uses a gsub!.
I've actually once had a discussion about this on ruby-forum when i faced this issue. We talked of a copy-on-write string. But i did not want to change my entire application.
It is inefficient for the API to keep returning dup()'ed strings. otoh, if the user accidentally changes the string (which she can), your API can throw an error or malfunction.
Always read the docs.
When learning a new language, after playing around with code-snippets I then usually read the language's reference.