1. Computing

Advanced Hash Usage

By

Advanced Hash Usage

While most uses of the Hash class are simple key/value storage and retrieval, there is more you can do with Hashes. This includes alternate syntaxes, default values and default procs.

Alternative Hash Literal Syntax

I'm sure you're all familiar with the "arrow syntax" for hash literals. Inside of curly braces, key/value pairs are separated with arrows (=>, equals and greater than symbols), and the key/value pairs themselves separated with commas. It looks like this.


hash = {
  :some_key => 'some value',
  :another_key => 22
}

However, the Ruby developers noticed a pattern with hash literals like these. In almost every single case, the keys were symbols. A special syntax came about in the 1.9.x branch to make hash literals with symbol keys a bit more compact. The above hash can be rewritten as so.


hash = {
  some_key:'some value',
  another_key:22
}

While there's no functional difference here, it is more compact and much better when the hash literal is crammed onto line line in a method call. For example, method_call( save:true, level:7 ). Note how this looks a lot like keyword parameters (which Ruby lacks) and the curly braces are gone here. This brings us to the next trick.

Keyword Parameters

Ruby doesn't have keyword parameters. A keyword parameter is a named parameter to a method that can be omitted, has a default value and can be passed if and when needed, and in any order. This ends the "giant parameter list" problem of many languages, where programmers are forced to memorize large parameters lists, their default values, etc. Instead, keyword parameters allow programmers to only utilize the parameters they're interested in.

While Ruby doesn't have keyword parameters, you can emulate them using ">a hash. If one of the parameters to your method has a default value of an empty hash, it will accept any "naked" key/value parameters in a method call. This is demonstrated in the following example.


def foo( name, opts={} )
  puts name
  puts opts.inspect
end

foo( 'test', :key => value )
foo( 'test', key:'value' )

As you can see, the opts parameter will soak up any extra "naked" key/value pairs. But, you ask, what if there are more than one hash parameter to the method? Let's explore that.


def bar( h1={}, h2={} }
  puts h1.inspect
  puts h2.inspect
end

bar( key:'value' )  # Ends up in h1
bar( {}, key:'value' )  # Ends up in h2, h1 is empty

As you can see, the "naked" parameters are taken by the first unassigned hash in the parameter list. In the first method call, the parameters end up in h1. However, in the second method call, an empty hash is passed in place of the first hash, and the key value pairs end up in h2.

One last thing: default values. Keyword parameters should have default values. You can't easily put these in the method definition, but you can put them at the top of the method body and use the merge method to merge the default parameters with the key/value pairs passed by the user.


def baz( _opts={} )
  opts = {
    :param => 'default'
  }.merge(_opts)

  puts opts.inspect
end

baz()  # :param will be 'default'
baz( param:'monkeys' )  # :param will be 'monkeys'

Default Values

What if you retrieve a value for a key that hasn't yet been stored? Normally, the hash will return nil. There's nothing there, nil is the very definition of nothing. You can change this a few ways.

First, you can pass a new default value to the Hash constructor. However, this requires you to construct your hash using the the constructor, and not a Hash literal. For example, if you want your default value to be the number 0 instead of nil, you can construct your hash like Hash.new(0). Now, any attempt to retrieve values where nothing has been stored yet will return 0.

If you already have a Hash and want to change this value, you can change the default attribute. The default object can be retrieved by calling this method, and set by calling the default= method.


h = { key:'value' }
h.default = 'monkeys'

puts h[:nothing]  # Will print 'monkeys'

And finally, if you want something even more flexible, you can set the default_proc attribute. If a default_proc attribute is set with a Proc object, it will be run every time an unknown key is accessed. This proc object takes two parameters, the hash itself (since the proc is defined outside of the hash, it won't have access to it normally) and the key trying to be accessed. Among other things, this can be used to implement exceptions for unknown keys.


h = {}
h.default_proc = Proc.new do|h, key|
  puts "Unknown key!  #{key}"
  # Just make up a value
  rand(0..10)
end

h[:unknown]  # Will issue warning and return random number
  1. About.com
  2. Computing
  3. Ruby
  4. Beginning Ruby
  5. Ruby's Basic Features
  6. Advanced Hash Usage

©2014 About.com. All rights reserved.