4

Hash initializers:

# this
animals = Hash.new { [] }
animals[:dogs] << :Scooby
animals[:dogs] << :Scrappy
animals[:dogs] << :DynoMutt
animals[:squirrels] << :Rocket
animals[:squirrels] << :Secret
animals #=> {}
# is not the same as this
animals = Hash.new { |_animals, type| _animals[type] = [] }
animals[:dogs] << :Scooby
animals[:dogs] << :Scrappy
animals[:dogs] << :DynoMutt
animals[:squirrels] << :Rocket
animals[:squirrels] << :Secret
animals #=> {:squirrels=>[:Rocket, :Secret], :dogs=>[:Scooby, :Scrappy, :DynoMutt]}

I saw someone post these on another question, but I don't understand why animals appears blank in the first case. If I type

animals[:dogs]

I get the appropriate array.

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
Jason Yu
  • 45
  • 3

3 Answers3

7

The first form specifies the block that returns a default value for a key that isn't found. That means that when you invoke animals[:dogs], there is no :dogs key in the hash, so your block gets invoked and animals[:dogs] evaluates to the result of your block, i.e. []. What happens then is that << :Scooby appends :Scooby to that empty list, which is then happily discarded.

The second form specifies the block that, when a key is requested and isn't found, receives as parameters the hash itself and the key that hasn't been found. It's a slightly more powerful version of the first constructor. The difference is in what your block does. In this second form, you modify the hash to associate [] with the key that hasn't been found. So now it's stored inside the hash and << :Scooby will store :Scooby there. Further calls for :dog won't trigger the block, because now :dog exists in the hash.

Vojislav Stojkovic
  • 8,043
  • 4
  • 35
  • 48
3

In the first case, the default value returned when the key does not exist is []. The various statements then successfully add various dogs and squirrels to the returned arrays.

However, at no point is a key ever created for :dogs or :squirrels.

In the second case, the block does store the new value back into a hash entry using the key.

One thing here that is somewhat interesting is how you keep getting a new empty array back in the first case. And the answer is: you did not pass [] as a parameter but as a block. That's executable and it is saved as a proc. Every time a key is not found, the proc runs again and generates a new [].

You can see this in operation, note the different object id values:

irb > t = Hash.new { [] }
 => {} 
irb > t[:a].object_id
 => 2149202180 
irb > t[:a].object_id
 => 2149192500 
DigitalRoss
  • 143,651
  • 25
  • 248
  • 329
0

The reason the first fails and the second doesn't is because of the block that is passed into Hash.new.

This block serves to define the default type returned when you access a key that does not yet exist. In the first example, there is no entry intializer, so each new key returns {}, or an empty Hash. Hash has no method <<, so it returns nothing.

The second case works correctly because the entry initializer is defined to be an empty Array. So, in this case, when you access animals[:dogs] for the first time, it returns [] an empty Array instead of {} an empty Hash. Array does has a method called <<, so it works successfully and shovels the symbol into the array at the specified key.

Hope this clears it up.

TheDelChop
  • 7,938
  • 4
  • 48
  • 70
  • No, the second case works because the block adds the new key to the hash. And `<<` is never used on a Hash in the first case, it is always used on an Array. The block doesn't define the return *type*, it defines the return *value*. – mu is too short Mar 02 '12 at 00:31
  • Actually, in the first case you don't get `{}` as a result. You get `[]`, because that's what you get as a result from the default value block. If you *did* get `{}`, then calling `<<` on it would throw a `NoMethodError` instead of returning nothing. – Vojislav Stojkovic Mar 02 '12 at 00:31