From: Kirk Haines Date: 2010-02-05T09:14:25+09:00 Subject: Re: Can Ruby Do This? On Thu, Feb 4, 2010 at 3:58 PM, Mr Bubb wrote: > In Perl, you can create a hash of arbitrary depth like so: > > $hash->{foo}->{bar}->{baz} = 123; > > I've tried this in ruby: > > hash[:foo][:bar][:baz] = 123 > > but I get nil.= errors > > I could of course do a check for null at each level, but oy, that's so > cumbersome. Is there a ruby way? Yes. You need to understand Ruby a little better, though. h = {} p h[:a] => nil That is, if you have a hash, and you reference an element in the hash that does not exist, the default behavior is for the hash to return nil. So, if you do this: h = {} p h[:a][:b] You will get this: NoMethodError: undefined method `[]' for nil:NilClass This is because h[:a] returns a nil, as you already discovered, and NilClass does not define an [] method. Since, in Ruby, you are just chaining method calls, your h[:a][:b][:c] style syntax can only work if the return element from any given level is an object that provides an [] method. Now, consider this example: h = Hash.new {|h,k| h[k] = {}} p h[:a] => {} Ah! Now we're on to something. h = Hash.new {|h,k| h[k] = {}} p h[:a][:b] => nil Hmmm. h = Hash.new {|h,k| h[k] = {}} p h[:a][:b][:c] => NoMethodError: undefined method `[]' for nil:NilClass Oh, dang. Do you see what's happening here? Hash's new method can take a block. If it is given a block, then it will call that block with arguments of the hash, itself, and the key that was given, if it's asked to lookup a key that does not exist in the hash. So, h[:a] ran that block, creating a value at :a => {}. h[:a][:b], though, gets us back to the default Hash behavior. Still, you see one solution here, I bet: h = Hash.new {|h,k| h[k] = Hash.new {|h2,k2| h2[k2] = {}}} h[:a][:b][:c] = 123 p h[:a][:b][:c] => 123 That works. It's kind of hard to easily read, though. So, you could rewrite it like this: h = Hash.new do |h1,k1| h1[k1] = Hash.new do |h2,k2| h2[k2] = {} end end That, at least, lets the eye follow along a little better, but it's still limited because you must decide what depth of auto initialization you are going to support ahead of time. If you try something like h[:a][:b][:c][:d][:e] you will again get that unpleasant NoMethodError. However, step back just a moment. The hash initializer accepts a block. And, if you look at the Hash methods, you'll see a Hash#default_proc. It does just what it sounds like it does -- it returns the default proc, which is what the block is turned into when the new method captures it and stores it. So, all you need to do is this: h = Hash.new {|hash,key| hash[key] = Hash.new(&hash.default_proc)} h[:a][:b][:c][:d][:e] = 12345 p h[:a][:b][:c][:d][:e] => 12345 Hopefully this helps you follow what's happening with Ruby's method calls and Hash initializers. Kirk Haines