3

I learned from this answer here that this is possible:

h = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }

h['bar'] # => {}
h['tar']['star']['par'] # => {}

Can someone explain how it works?

Community
  • 1
  • 1
unpangloss
  • 409
  • 9
  • 18

2 Answers2

16

Hashes have a thing called a default_proc, which is simply a proc that Ruby runs when you try to access a hash key that doesn't exist. This proc receives both the hash itself and the target key as parameters.

You can set a Hash's default_proc at any time. Passing a block parameter to Hash.new simply allows you to initialize a Hash and set its default_proc in one step:

h = Hash.new
h.default_proc = proc{ |hash, key| hash[key] = 'foo' }

# The above is equivalent to:

h = Hash.new{ |hash, key| hash[key] = 'foo' }

We can also access the default proc for a hash by calling h.default_proc. Knowing this, and knowing that the ampersand (&) allows a proc passed as a normal parameter to be treated as a block parameter, we can now explain how this code works:

cool_hash = Hash.new{ |h, k| h[k] = Hash.new(&h.default_proc) }

The block passed to Hash.new will be called when we try to access a key that doesn't exist. This block will receive the hash itself as h, and the key we tried to access as k. We respond by setting h[k] (that is, the value of the key we're trying to access) to a new hash. Into the constructor of this new hash, we pass the "parent" hash's default_proc, using an ampersand to force it to be interpreted as a block parameter. This is the equivalent of doing the following, to an infinite depth:

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

The end result is that the key we tried to access was initialized to a new Hash, which itself will initialize any "not found" keys to a new Hash, which itself will have the same behavior, etc. It's hashes all the way down.

DigitalCora
  • 2,222
  • 1
  • 16
  • 24
  • Very clear explanation, Grantovich. One detail: doesn't `h.default_proc` return a block, and the `&` converts it to a proc? – Cary Swoveland Nov 23 '13 at 04:56
  • 1
    `h.default_proc` returns a proc, as the name suggests. Blocks literally *are* procs, so there's no conversion being done per se; "block" is just a name for the proc that gets called when we `yield`, and can be passed to any method using the "block syntax". When we want to pass a block non-literally, we have to use `&` to tell Ruby "treat this proc as though I had passed it using the block syntax". [This blog post may demystify things further.](http://www.skorks.com/2013/04/ruby-ampersand-parameter-demystified/) – DigitalCora Nov 23 '13 at 05:43
1

In this code you create hashes by chain, so that any link of chain would have same default_proc

So, default_proc of h and h['bar'] and so far will be the same - it will return new instance of Hash with this default_proc

Ivan Antropov
  • 414
  • 2
  • 14