Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ruby hash autovivification (facets)

Tags:

ruby

Here is a clever trick to enable hash autovivification in ruby (taken from facets):

  # File lib/core/facets/hash/autonew.rb, line 19
  def self.autonew(*args)
    leet = lambda { |hsh, key| hsh[key] = new( &leet ) }
    new(*args,&leet)
  end

Although it works (of course), I find it really frustrating that I can't figure out how this two liner does what it does.

leet is put as a default value. So that then just accessing h['new_key'] somehow brings it up and creates 'new_key' => {}

Now, I'd expect h['new_key'] returning default value object as opposed to evaluating it. That is, 'new_key' => {} is not automatically created. So how does leet actually get called? Especially with two parameters?

like image 886
artemave Avatar asked Oct 01 '09 12:10

artemave


People also ask

What characterizes a hash Ruby?

A Hash is a dictionary-like collection of unique keys and their values. Also called associative arrays, they are similar to Arrays, but where an Array uses integers as its index, a Hash allows you to use any object type. Hashes enumerate their values in the order that the corresponding keys were inserted.

Do Ruby hashes preserve order?

Since Ruby 1.9, hashes maintain the order in which they're stored.

What hash function does Ruby use?

The hashing functions included in Ruby's digest include: MD5, RIPEMED-160, SHA1, and SHA2. Each hashing function will accept an input variable, and the output can be returned in either a digest, hexidecimal, or “bubble babble” format.


2 Answers

The standard new method for Hash accepts a block. This block is called in the event of trying to access a key in the Hash which does not exist. The block is passed the Hash itself and the key that was requested (the two parameters) and should return the value that should be returned for the requested key.

You will notice that the leet lambda does 2 things. It returns a new Hash with leet itself as the block for handling defaults. This is the behaviour which allows autonew to work for Hashes of arbitrary depth. It also assigns this new Hash to hsh[key] so that next time you request the same key you will get the existing Hash rather than a new one being created.

like image 134
mikej Avatar answered Oct 21 '22 22:10

mikej


It's also worth noting that this code can be made into a one-liner as follows:

def self.autonew(*args)
  new(*args){|hsh, key| hsh[key] = Hash.new(&hsh.default_proc) }
end

The call to Hash#default_proc returns the proc that was used to create the parent, so we have a nice recursive setup here.

I talk about a similar case to this on my blog.

like image 30
Peter Wagenet Avatar answered Oct 21 '22 21:10

Peter Wagenet