Defaults for Hash

As a follow up to my first post I’d like to quickly run over the “default value” related features of Ruby’s Hash and explain when to use them.

Hash#default

totals = Hash.new(0)
totals[:jeff] # => 0
totals[:ann] += 1 # => 1

Use this when:

Do not use this when:

scored = Hash.new([])
scored[:jeff] # => []
scored[:ann] << 'A+'
scored[:jeff] # => ['A+']
scored # => {}

💩

Hash#default_proc

fizz_bang = Hash.new { |hash, key| hash[key] = fizz_and_or_bang(key) }
fizz_bang[3] # => 'Fizz'
fizz_bang[5] # => 'Bang'

Use this when:

Don’t user this when:

Changing default methods

It’s possible to change the default_proc and default value. The last assignment will be what’s used.

h = Hash.new
h[:jeff] # => nil
h.default = 'default'
h[:jeff] # => 'default'
h.default_proc = -> (*) { 'default_proc' }
h[:jeff] # => 'default_proc'
h.default = nil
h[:jeff] # => nil
h # => {}

Hash#fetch

New Ruby programmers probably do something like this:

hash = {}
hash[:key] ||= 1

This works fine until some falsy values are introduced:

hash = {1 => nil, 2 => false}
hash[1] ||= 1
hash[2] ||= 2
hash # {1 => 1, 2 => 2}

😩

When using || your falsy values will be overwritten. This is most probably not your intention. Instead of that use Hash#fetch.

hash = {1 => nil, 2 => false}
hash.fetch(1) { 1 }
hash.fetch(2) { 2 }
hash # {1 => nil, 2 => false}

Hash#fetch is best used when:

It’s also great to ensure a key is present

Hash.new.fetch(:a_missing_key)
# KeyError: key not found: :a_missing_key

Don’t use it when:

Hash#fetch also has a second notation for the default value:

Hash.new.fetch(:a_missing_key, 'default value') # => 'default value'

I only use the notation with a block. There are no real benefits to the other notation except maybe some performance difference in super simple cases. In most cases though, the block version will be the better choice. It will postpone the evaluation of the default value until it’s needed; and - at lest to me - it looks better. 😍

Some idiosyncratic fun

So, what do you think this will do?

Hash.new('default value') { |hash, key| hash[key] = key.to_s }

How about this?

Hash.new.fetch('default value') { 'value' }

👉 Try it yourself 👈, I found it very surprising 😃