I just stumbled across this surprising behavior myself. It's the first
counter-intuitive mechanism I have come across in my short sweet
experience with Ruby.
Check this thread for an elaborate discussion (in English) of this
behavior:
http://www.ruby-forum.com/topic/134424#new
Here's my take:
Before we look at your case, let's look at a case that actually works as
you'd expect: initializing a hash with a Fixnum:
Code
h = Hash.new(0)
puts "h['key1']: #{h['key1']}"
puts "h['key2']: #{h['key2']}"
h['key1'] += 1
puts "after updating key1"
puts "h['key1']: #{h['key1']}"
puts "h['key2']: #{h['key2']}"
Result:
h['key1']: 0
h['key2']: 0
after updating key1
h['key1']: 1
h['key2']: 0
Perfect! Mighty handy for word count programs and all sorts of other use
cases.
Which would lead you to expect the following behavior when you
initialize a hash with an empty array, then append:
Code
h = Hash.new([])
puts "h['key1']: #{h['key1']}"
puts "h['key2']: #{h['key2']}"
h['key1'] << 1
puts "after updating key1"
puts "h['key1']: #{h['key1']}"
puts "h['key2']: #{h['key2']}"
Result
h['key1']: []
h['key2']: []
after updating key1
h['key1']: [1]
h['key2']: [] #<-- what you'd expect, but NOT what you get
The actual result is the following:
h['key1']: []
h['key2']: []
after updating key1
h['key1']: [1]
h['key2']: [1]
. . . and so on
The problem is that when you initialize a hash with a mutable default
value, all of the defaults are actually references to THE SAME OBJECT.
So when you append to the default array in one hash value, you're
actually changing them all. Witness:
puts "#{h['key1'].object_id}"
puts "#{h['key2'].object_id}"
puts "#{h['key3'].object_id}"
Result:
116528
116528
116528
By contrast, when you update a value with the += construction rather
than <<, you're actually creating a new array object for that value. So
that particular one is no longer referring to the default value.
The thread referred to above mentions other ways to get what you'd
expect with a default empty array. Still, I gotta admit that I simply
don't understand why Hash.new([]) works the way it does. Who would want
to create a Hash table where changing a single value can potentially
change all other values, past, present, and to come. Talk about side
effects gone wild!
If anyone can explain the rationale for this behavior,I'd really
appreciate it. I'm probably just missing something.
···
--
Posted via http://www.ruby-forum.com/.