8

Ruby lets you define default values for hashes:

h=Hash.new(['alright'])
h['meh'] # => ["alright"]

Assignment of a value shows up when displaying the hash, but a modified default does not. Where's 'bad'?

h['good']=['fine','dandy']
h['bad'].push('unhappy')
h # => {"good"=>["fine", "dandy"]}

'bad' shows up if we explicitly ask.

h['bad'] # => ["alright", "unhappy"]

Why does the modified default value not show up when displaying the hash?

sawa
  • 165,429
  • 45
  • 277
  • 381
Dragon
  • 2,017
  • 1
  • 19
  • 35

2 Answers2

11

Hash's default value doesn't work like you're expecting it to. When you say h[k], the process goes like this:

  1. If we have a k key, return its value.
  2. If we have a default value for the Hash, return that default value.
  3. If we have a block for providing default values, execute the block and return its return value.

Note that (2) and (3) say nothing at all about inserting k into the Hash. The default value essentially turns h[k] into this:

h.has_key?(k) ? h[k] : the_default_value

So simply accessing a non-existant key and getting the default value back won't add the missing key to the Hash.

Furthermore, anything of the form:

Hash.new([ ... ])
# or
Hash.new({ ... })

is almost always a mistake as you'll be sharing exactly the same default Array or Hash for for all default values. For example, if you do this:

h = Hash.new(['a'])
h[:k].push('b')

Then h[:i], h[:j], ... will all return ['a', 'b'] and that's rarely what you want.

I think you're looking for the block form of the default value:

h = Hash.new { |h, k| h[k] = [ 'alright' ] }

That will do two things:

  1. Accessing a non-existent key will add that key to the Hash and it will have the provided Array as its value.
  2. All of the default values will be distinct objects so altering one will not alter the rest.
mu is too short
  • 426,620
  • 70
  • 833
  • 800
2

What's happened is that you have modified the default value of the hash, by pushing 'unhappy' onto h['bad']. What you haven't done is actually added 'bad' to the hash, which is why it doesn't show up when you inspect h.

After all the code you supplied, I tried this:

>> p h['bleh']
=> ["allright", "unhappy"]

Which certainly suggests to me that the default value has been changed. In answer to your question 'Why does the modified default not show up when displaying the hash?', you would have to add an element to it, rather than just accessing it:

>> h['bleh']  # Doesn't add 'bleh' to the hash
>> p h
=> {"good"=>["fine", "dandy"]} # See, no extra values

>> h['bleh'] = h.default  # Does add a new key with the default value
>> p h
=> {"good"=>["fine", "dandy"], "bleh"=>["allright", "unhappy"]}
Jon M
  • 11,669
  • 3
  • 41
  • 47
  • 1
    Wow. I stand totally corrected. But now I'm stumped as to WHY that's the case. [] is a method on a hash object, that returns a result. Why would sending push to that change the default hash? Time to start digging through the ruby source code... – Marc Talbot Feb 29 '12 at 03:16
  • Got it. Hash object still contains reference to the array that is being updated. Deleting my answer. :) – Marc Talbot Feb 29 '12 at 03:20
  • 1
    I'm assuming that the default value is a reference to the array passed into Hash's constructor. When you 'push' onto that, you're directly modifying the array rather than creating a copy. For a simpler example: `h = Hash.new('hello'); h['something'] << 'aaa'; puts h.default` will return 'helloaaa' – Jon M Feb 29 '12 at 03:22