Ruby Sorbet Hash Typecheck - ruby

I want to understand why Sorbet does not complain for this example: Example:
sig {params(x: T::Hash[String, String]).void}
def foo(x)
x.each do |k, v|
puts "key = #{k}, value = #{v}"
end
end
hash = {}
hash[1] = 1
foo(hash) # I'd expect this to fail to type-check
I have declared foo to take in a hash of [String, String] but I am passing a hash of [Integer, Integer]. I thought Sorbet would complain here...
Is there a way to make it error out for these kinds of scenarios?

The problem is that hash is not of type T::Hash[Integer, Integer], but T::Hash[T.untyped, T.untyped]. You can see that by using T.reveal_type:
hash = {}
hash[1] = 1
T.reveal_type(hash) # Revealed type: {} (shape of T::Hash[T.untyped, T.untyped])
This happens because Sorbet doesn't have information about your hash when it's created. The solution is to explicitly initialize it:
hash = T::Hash[Integer, Integer].new
hash[1] = 1
foo(hash) # Expected T::Hash[String, String] but found T::Hash[Integer, Integer] for argument x
You can see the full code here
The underlying reason is because the hash is initially interpreted as a Shape (see https://sorbet.org/docs/shapes), without any type information.

Related

Give the code below, I was told that if a hash is called and pass through as argument, I can't return Nil. Can anyone explain to me how come?

def key_for_min_value(name_hash)
name_hash.max_by {|k, v| 0-v}[0]
end
This was my code to fulfill the test suite for finding the lowest value of a hash (this was for one of my lessons online).
I know there are much easier ways to do this but I had some restrictions, as you can see below:
**A Few Restrictions:
We want you to build this on your own. Some of the following methods are helpful but off limits for this exercise. (We'll cover a few below in more depth in subsequent lessons).
I could not use keys, values, min, sort, min_by to make it pass.
This code returned the key with the lowest value (a hash of key ==> integers) but here was the requirement I could not figure out.
If the method is called and passed an argument of an empty hash, it should return nil.
Only first month programming, so this may be obvious but is there a way to return nil for an empty hash, and keep my existing code intact?
Thanks for your help
To a beginner programmer I would recommend to print all intermediate results of expressions, or work in IRB.
def key_for_min_value(name_hash)
puts
puts "in key_for_min_value with parameter #{name_hash}"
# puts "about to return nil" if name_hash.empty?
# return nil if name_hash.empty?
name_hash.max_by { | item | puts "item=#{item}" }
max = name_hash.max_by do | k, v |
puts "k=#{k} v=#{v} 0 - v = #{0 - v}"
0 - v
end
puts "max=#{max.inspect}, class of value returned by max_by : #{max.class}"
result = name_hash.max_by {|k, v| 0-v}[0]
puts "result=#{result.inspect}"
result
end
key_for_min_value({a: 1, b: 2, c: 3})
key_for_min_value({})
Execution :
$ ruby -w t.rb
in key_for_min_value with parameter {:a=>1, :b=>2, :c=>3}
item=[:a, 1]
item=[:b, 2]
item=[:c, 3]
k=a v=1 0 - v = -1
k=b v=2 0 - v = -2
k=c v=3 0 - v = -3
max=[:a, 1], class of value returned by max_by : Array
result=:a
in key_for_min_value with parameter {}
max=nil, class of value returned by max_by : NilClass
t.rb:15:in `key_for_min_value': undefined method `[]' for nil:NilClass (NoMethodError)
from t.rb:21:in `<main>'
The documentation of enum.max_by says :
Returns the item corresponding to the largest value returned by the
block.
But if the enum is empty, it returns nil, from which you fetch element [0], which causes the error because there is no such method in the NilClass.
If you add return nil if name_hash.empty? at the beginning of the method, you prevent it to happen (with two uncommented lines) :
$ ruby -w t.rb
in key_for_min_value with parameter {:a=>1, :b=>2, :c=>3}
...
in key_for_min_value with parameter {}
about to return nil
There a lot of different possibilities to do what you want. The most obvious one is to literally translate the sentence: "return nil if the hash is empty" into Ruby:
def key_for_min_value(name_hash)
return nil if name_hash.empty?
name_hash.max_by {|k, v| 0-v}[0]
end
Another possibility would be to use the safe navigation operator:
def key_for_min_value(name_hash)
name_hash.max_by {|k, v| 0-v}&.[](0)
end
Yet another way would be to ensure that the value you are trying to index into is never nil:
def key_for_min_value(name_hash)
(name_hash.max_by {|k, v| 0-v} || [])[0]
end
# or
def key_for_min_value(name_hash)
Array(name_hash.max_by {|k, v| 0-v})[0]
end

How to make Ruby var= return value assigned, not value passed in?

There's a nice idiom for adding to lists stored in a hash table:
(hash[key] ||= []) << new_value
Now, suppose I write a derivative hash class, like the ones found in Hashie, which does a deep-convert of any hash I store in it. Then what I store will not be the same object I passed to the = operator; Hash may be converted to Mash or Clash, and arrays may be copied.
Here's the problem. Ruby apparently returns, from the var= method, the value passed in, not the value that's stored. It doesn't matter what the var= method returns. The code below demonstrates this:
class C
attr_reader :foo
def foo=(value)
#foo = (value.is_a? Array) ? (value.clone) : value
end
end
c=C.new
puts "assignment: #{(c.foo ||= []) << 5}"
puts "c.foo is #{c.foo}"
puts "assignment: #{(c.foo ||= []) << 6}"
puts "c.foo is #{c.foo}"
output is
assignment: [5]
c.foo is []
assignment: [6]
c.foo is [6]
When I posted this as a bug to Hashie, Danielle Sucher explained what was happening and pointed out that "foo.send :bar=, 1" returns the value returned by the bar= method. (Hat tip for the research!) So I guess I could do:
c=C.new
puts "clunky assignment: #{(c.foo || c.send(:foo=, [])) << 5}"
puts "c.foo is #{c.foo}"
puts "assignment: #{(c.foo || c.send(:foo=, [])) << 6}"
puts "c.foo is #{c.foo}"
which prints
clunky assignment: [5]
c.foo is [5]
assignment: [5, 6]
c.foo is [5, 6]
Is there any more elegant way to do this?
Assignments evaluate to the value that is being assigned. Period.
In some other languages, assignments are statements, so they don't evaluate to anything. Those are really the only two sensible choices. Either don't evaluate to anything, or evaluate to the value being assigned. Everything else would be too surprising.
Since Ruby doesn't have statements, there is really only one choice.
The only "workaround" for this is: don't use assignment.
c.foo ||= []
c.foo << 5
Using two lines of code isn't the end of the world, and it's easier on the eyes.
The prettiest way to do this is to use default value for hash:
# h = Hash.new { [] }
h = Hash.new { |h,k| h[k] = [] }
But be ware that you cant use Hash.new([]) and then << because of way how Ruby store variables:
h = Hash.new([])
h[:a] # => []
h[:b] # => []
h[:a] << 10
h[:b] # => [10] O.o
it's caused by that Ruby store variables by reference, so as we created only one array instance, ad set it as default value then it will be shared between all hash cells (unless it will be overwrite, i.e. by h[:a] += [10]).
It is solved by using constructor with block (doc) Hash.new { [] }. With this each time when new key is created block is called and each value is different array.
EDIT: Fixed error that #Uri Agassi is writing about.

Ruby hash with multiple keys pointing to the same value

I am looking for a way to have, I would say synonym keys in the hash.
I want multiple keys to point to the same value, so I can read/write a value through any of these keys.
As example, it should work like that (let say :foo and :bar are synonyms)
hash[:foo] = "foo"
hash[:bar] = "bar"
puts hash[:foo] # => "bar"
Update 1
Let me add couple of details. The main reason why I need these synonyms, because I receive keys from external source, which I can't control, but multiple keys could actually be associated with the same value.
Rethink Your Data Structure
Depending on how you want to access your data, you can make either the keys or the values synonyms by making them an array. Either way, you'll need to do more work to parse the synonyms than the definitional word they share.
Keys as Definitions
For example, you could use the keys as the definition for your synonyms.
# Create your synonyms.
hash = {}
hash['foo'] = %w[foo bar]
hash
# => {"foo"=>["foo", "bar"]}
# Update the "definition" of your synonyms.
hash['baz'] = hash.delete('foo')
hash
# => {"baz"=>["foo", "bar"]}
Values as Definitions
You could also invert this structure and make your keys arrays of synonyms instead. For example:
hash = {["foo", "bar"]=>"foo"}
hash[hash.rassoc('foo').first] = 'baz'
=> {["foo", "bar"]=>"baz"}
You could subclass hash and override [] and []=.
class AliasedHash < Hash
def initialize(*args)
super
#aliases = {}
end
def alias(from,to)
#aliases[from] = to
self
end
def [](key)
super(alias_of(key))
end
def []=(key,value)
super(alias_of(key), value)
end
private
def alias_of(key)
#aliases.fetch(key,key)
end
end
ah = AliasedHash.new.alias(:bar,:foo)
ah[:foo] = 123
ah[:bar] # => 123
ah[:bar] = 456
ah[:foo] # => 456
What you can do is completely possible as long as you assign the same object to both keys.
variable_a = 'a'
hash = {foo: variable_a, bar: variable_a}
puts hash[:foo] #=> 'a'
hash[:bar].succ!
puts hash[:foo] #=> 'b'
This works because hash[:foo] and hash[:bar] both refer to the same instance of the letter a via variable_a. This however wouldn't work if you used the assignment hash = {foo: 'a', bar: 'a'} because in that case :foo and :bar refer to different instance variables.
The answer to your original post is:
hash[:foo] = hash[:bar]
and
hash[:foo].__id__ == hash[:bar].__id__it
will hold true as long as the value is a reference value (String, Array ...) .
The answer to your Update 1 could be:
input.reduce({ :k => {}, :v => {} }) { |t, (k, v)|
t[:k][t[:v][v] || k] = v;
t[:v][v] = k;
t
}[:k]
where «input» is an abstract enumerator (or array) of your input data as it comes [key, value]+, «:k» your result, and «:v» an inverted hash that serves the purpose of finding a key if its value is already present.

Is there a built-in lazy Hash in Ruby?

I need to populate a Hash with various values. Some of values are accessed often enough and another ones really seldom.
The issue is, I'm using some computation to get values and populating the Hash becomes really slow with multiple keys.
Using some sort of cache is not a option in my case.
I wonder how to make the Hash compute the value only when the key is firstly accessed and not when it is added?
This way, seldom used values wont slow down the filling process.
I'm looking for something that is "kinda async" or lazy access.
There are many different ways to approach this. I recommend using an instance of a class that you define instead of a Hash. For example, instead of...
# Example of slow code using regular Hash.
h = Hash.new
h[:foo] = some_long_computation
h[:bar] = another_long_computation
# Access value.
puts h[:foo]
... make your own class and define methods, like this...
class Config
def foo
some_long_computation
end
def bar
another_long_computation
end
end
config = Config.new
puts config.foo
If you want a simple way to cache the long computations or it absolutely must be a Hash, not your own class, you can now wrap the Config instance with a Hash.
config = Config.new
h = Hash.new {|h,k| h[k] = config.send(k) }
# Access foo.
puts h[:foo]
puts h[:foo] # Not computed again. Cached from previous access.
One issue with the above example is that h.keys will not include :bar because you haven't accessed it yet. So you couldn't, for example, iterate over all the keys or entries in h because they don't exist until they're actually accessed. Another potential issue is that your keys need to be valid Ruby identifiers, so arbitrary String keys with spaces won't work when defining them on Config.
If this matters to you, there are different ways to handle it. One way you can do it is to populate your hash with thunks and force the thunks when accessed.
class HashWithThunkValues < Hash
def [](key)
val = super
if val.respond_to?(:call)
# Force the thunk to get actual value.
val = val.call
# Cache the actual value so we never run long computation again.
self[key] = val
end
val
end
end
h = HashWithThunkValues.new
# Populate hash.
h[:foo] = ->{ some_long_computation }
h[:bar] = ->{ another_long_computation }
h["invalid Ruby name"] = ->{ a_third_computation } # Some key that's an invalid ruby identifier.
# Access hash.
puts h[:foo]
puts h[:foo] # Not computed again. Cached from previous access.
puts h.keys #=> [:foo, :bar, "invalid Ruby name"]
One caveat with this last example is that it won't work if your values are callable because it can't tell the difference between a thunk that needs to be forced and a value.
Again, there are ways to handle this. One way to do it would be to store a flag that marks whether a value has been evaluated. But this would require extra memory for every entry. A better way would be to define a new class to mark that a Hash value is an unevaluated thunk.
class Unevaluated < Proc
end
class HashWithThunkValues < Hash
def [](key)
val = super
# Only call if it's unevaluated.
if val.is_a?(Unevaluated)
# Force the thunk to get actual value.
val = val.call
# Cache the actual value so we never run long computation again.
self[key] = val
end
val
end
end
# Now you must populate like so.
h = HashWithThunkValues.new
h[:foo] = Unevaluated.new { some_long_computation }
h[:bar] = Unevaluated.new { another_long_computation }
h["invalid Ruby name"] = Unevaluated.new { a_third_computation } # Some key that's an invalid ruby identifier.
h[:some_proc] = Unevaluated.new { Proc.new {|x| x + 2 } }
The downside of this is that now you have to remember to use Unevaluted.new when populating your Hash. If you want all values to be lazy, you could override []= also. I don't think it would actually save much typing because you'd still need to use Proc.new, proc, lambda, or ->{} to create the block in the first place. But it might be worthwhile. If you did, it might look something like this.
class HashWithThunkValues < Hash
def []=(key, val)
super(key, val.respond_to?(:call) ? Unevaluated.new(&val) : val)
end
end
So here is the full code.
class HashWithThunkValues < Hash
# This can be scoped inside now since it's not used publicly.
class Unevaluated < Proc
end
def [](key)
val = super
# Only call if it's unevaluated.
if val.is_a?(Unevaluated)
# Force the thunk to get actual value.
val = val.call
# Cache the actual value so we never run long computation again.
self[key] = val
end
val
end
def []=(key, val)
super(key, val.respond_to?(:call) ? Unevaluated.new(&val) : val)
end
end
h = HashWithThunkValues.new
# Populate.
h[:foo] = ->{ some_long_computation }
h[:bar] = ->{ another_long_computation }
h["invalid Ruby name"] = ->{ a_third_computation } # Some key that's an invalid ruby identifier.
h[:some_proc] = ->{ Proc.new {|x| x + 2 } }
You can define your own indexer with something like this:
class MyHash
def initialize
#cache = {}
end
def [](key)
#cache[key] || (#cache[key] = compute(key))
end
def []=(key, value)
#cache[key] = value
end
def compute(key)
#cache[key] = 1
end
end
and use it as follows:
1.9.3p286 :014 > hash = MyHash.new
=> #<MyHash:0x007fa0dd03a158 #cache={}>
1.9.3p286 :019 > hash["test"]
=> 1
1.9.3p286 :020 > hash
=> #<MyHash:0x007fa0dd03a158 #cache={"test"=>1}>
you can use this:
class LazyHash < Hash
def [] key
(_ = (#self||{})[key]) ?
((self[key] = _.is_a?(Proc) ? _.call : _); #self.delete(key)) :
super
end
def lazy_update key, &proc
(#self ||= {})[key] = proc
self[key] = proc
end
end
Your lazy hash will behave exactly as a normal Hash, cause it is actually a real Hash.
See live demo here
*** UPDATE - answering to nested procs question ***
Yes, it would work, but it is cumbersome.
See updated answer.
Use lazy_update instead of []= to add "lazy" values to your hash.
This isn't strictly an answer to the body of your question, but Enumerable::Lazy will definitely be a part of Ruby 2.0. This will let you do lazy evaluation on iterator compositions:
lazy = [1, 2, 3].lazy.select(&:odd?)
# => #<Enumerable::Lazy: #<Enumerator::Generator:0x007fdf0b864c40>:each>
lazy.to_a
# => [40, 50]

Accessing elements of nested hashes in ruby [duplicate]

This question already has answers here:
Ruby Style: How to check whether a nested hash element exists
(16 answers)
How to avoid NoMethodError for nil elements when accessing nested hashes? [duplicate]
(4 answers)
Closed 7 years ago.
I'm working a little utility written in ruby that makes extensive use of nested hashes. Currently, I'm checking access to nested hash elements as follows:
structure = { :a => { :b => 'foo' }}
# I want structure[:a][:b]
value = nil
if structure.has_key?(:a) && structure[:a].has_key?(:b) then
value = structure[:a][:b]
end
Is there a better way to do this? I'd like to be able to say:
value = structure[:a][:b]
And get nil if :a is not a key in structure, etc.
Traditionally, you really had to do something like this:
structure[:a] && structure[:a][:b]
However, Ruby 2.3 added a method Hash#dig that makes this way more graceful:
structure.dig :a, :b # nil if it misses anywhere along the way
There is a gem called ruby_dig that will back-patch this for you.
Hash and Array have a method called dig.
value = structure.dig(:a, :b)
It returns nil if the key is missing at any level.
If you are using a version of Ruby older than 2.3, you can install a gem such as ruby_dig or hash_dig_and_collect, or implement this functionality yourself:
module RubyDig
def dig(key, *rest)
if value = (self[key] rescue nil)
if rest.empty?
value
elsif value.respond_to?(:dig)
value.dig(*rest)
end
end
end
end
if RUBY_VERSION < '2.3'
Array.send(:include, RubyDig)
Hash.send(:include, RubyDig)
end
The way I usually do this these days is:
h = Hash.new { |h,k| h[k] = {} }
This will give you a hash that creates a new hash as the entry for a missing key, but returns nil for the second level of key:
h['foo'] -> {}
h['foo']['bar'] -> nil
You can nest this to add multiple layers that can be addressed this way:
h = Hash.new { |h, k| h[k] = Hash.new { |hh, kk| hh[kk] = {} } }
h['bar'] -> {}
h['tar']['zar'] -> {}
h['scar']['far']['mar'] -> nil
You can also chain indefinitely by using the default_proc method:
h = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
h['bar'] -> {}
h['tar']['star']['par'] -> {}
The above code creates a hash whose default proc creates a new Hash with the same default proc. So, a hash created as a default value when a lookup for an unseen key occurs will have the same default behavior.
EDIT: More details
Ruby hashes allow you to control how default values are created when a lookup occurs for a new key. When specified, this behavior is encapsulated as a Proc object and is reachable via the default_proc and default_proc= methods. The default proc can also be specified by passing a block to Hash.new.
Let's break this code down a little. This is not idiomatic ruby, but it's easier to break it out into multiple lines:
1. recursive_hash = Hash.new do |h, k|
2. h[k] = Hash.new(&h.default_proc)
3. end
Line 1 declares a variable recursive_hash to be a new Hash and begins a block to be recursive_hash's default_proc. The block is passed two objects: h, which is the Hash instance the key lookup is being performed on, and k, the key being looked up.
Line 2 sets the default value in the hash to a new Hash instance. The default behavior for this hash is supplied by passing a Proc created from the default_proc of the hash the lookup is occurring in; ie, the default proc the block itself is defining.
Here's an example from an IRB session:
irb(main):011:0> recursive_hash = Hash.new do |h,k|
irb(main):012:1* h[k] = Hash.new(&h.default_proc)
irb(main):013:1> end
=> {}
irb(main):014:0> recursive_hash[:foo]
=> {}
irb(main):015:0> recursive_hash
=> {:foo=>{}}
When the hash at recursive_hash[:foo] was created, its default_proc was supplied by recursive_hash's default_proc. This has two effects:
The default behavior for recursive_hash[:foo] is the same as recursive_hash.
The default behavior for hashes created by recursive_hash[:foo]'s default_proc will be the same as recursive_hash.
So, continuing in IRB, we get the following:
irb(main):016:0> recursive_hash[:foo][:bar]
=> {}
irb(main):017:0> recursive_hash
=> {:foo=>{:bar=>{}}}
irb(main):018:0> recursive_hash[:foo][:bar][:zap]
=> {}
irb(main):019:0> recursive_hash
=> {:foo=>{:bar=>{:zap=>{}}}}
I made rubygem for this. Try vine.
Install:
gem install vine
Usage:
hash.access("a.b.c")
I think one of the most readable solutions is using Hashie:
require 'hashie'
myhash = Hashie::Mash.new({foo: {bar: "blah" }})
myhash.foo.bar
=> "blah"
myhash.foo?
=> true
# use "underscore dot" for multi-level testing
myhash.foo_.bar?
=> true
myhash.foo_.huh_.what?
=> false
value = structure[:a][:b] rescue nil
Solution 1
I suggested this in my question before:
class NilClass; def to_hash; {} end end
Hash#to_hash is already defined, and returns self. Then you can do:
value = structure[:a].to_hash[:b]
The to_hash ensures that you get an empty hash when the previous key search fails.
Solution2
This solution is similar in spirit to mu is too short's answer in that it uses a subclass, but still somewhat different. In case there is no value for a certain key, it does not use a default value, but rather creates a value of empty hash, so that it does not have the problem of confusion in assigment that DigitalRoss's answer has, as was pointed out by mu is too short.
class NilFreeHash < Hash
def [] key; key?(key) ? super(key) : self[key] = NilFreeHash.new end
end
structure = NilFreeHash.new
structure[:a][:b] = 3
p strucrture[:a][:b] # => 3
It departs from the specification given in the question, though. When an undefined key is given, it will return an empty hash instread of nil.
p structure[:c] # => {}
If you build an instance of this NilFreeHash from the beginning and assign the key-values, it will work, but if you want to convert a hash into an instance of this class, that may be a problem.
You could just build a Hash subclass with an extra variadic method for digging all the way down with appropriate checks along the way. Something like this (with a better name of course):
class Thing < Hash
def find(*path)
path.inject(self) { |h, x| return nil if(!h.is_a?(Thing) || h[x].nil?); h[x] }
end
end
Then just use Things instead of hashes:
>> x = Thing.new
=> {}
>> x[:a] = Thing.new
=> {}
>> x[:a][:b] = 'k'
=> "k"
>> x.find(:a)
=> {:b=>"k"}
>> x.find(:a, :b)
=> "k"
>> x.find(:a, :b, :c)
=> nil
>> x.find(:a, :c, :d)
=> nil
This monkey patch function for Hash should be easiest (at least for me). It also doesn't alter structure i.e. changing nil's to {}. It would still also apply even if you're reading a tree from a raw source e.g. JSON. It also doesn't need to produce empty hash objects as it goes or parse a string. rescue nil was actually a good easy solution for me as I'm brave enough for such a low risk but I find it to essentially have a drawback with performance.
class ::Hash
def recurse(*keys)
v = self[keys.shift]
while keys.length > 0
return nil if not v.is_a? Hash
v = v[keys.shift]
end
v
end
end
Example:
> structure = { :a => { :b => 'foo' }}
=> {:a=>{:b=>"foo"}}
> structure.recurse(:a, :b)
=> "foo"
> structure.recurse(:a, :x)
=> nil
What's also good is that you can play around saved arrays with it:
> keys = [:a, :b]
=> [:a, :b]
> structure.recurse(*keys)
=> "foo"
> structure.recurse(*keys, :x1, :x2)
=> nil
The XKeys gem will read and auto-vivify-on-write nested hashes (::Hash) or hashes and arrays (::Auto, based on the key/index type) with a simple, clear, readable, and compact syntax by enhancing #[] and #[]=. The sentinel symbol :[] will push onto the end of an array.
require 'xkeys'
structure = {}.extend XKeys::Hash
structure[:a, :b] # nil
structure[:a, :b, :else => 0] # 0 (contextual default)
structure[:a] # nil, even after above
structure[:a, :b] = 'foo'
structure[:a, :b] # foo
You can use the andand gem, but I'm becoming more and more wary of it:
>> structure = { :a => { :b => 'foo' }} #=> {:a=>{:b=>"foo"}}
>> require 'andand' #=> true
>> structure[:a].andand[:b] #=> "foo"
>> structure[:c].andand[:b] #=> nil
There is the cute but wrong way to do this. Which is to monkey-patch NilClass to add a [] method that returns nil. I say it is the wrong approach because you have no idea what other software may have made a different version, or what behavior change in a future version of Ruby can be broken by this.
A better approach is to create a new object that works a lot like nil but supports this behavior. Make this new object the default return of your hashes. And then it will just work.
Alternately you can create a simple "nested lookup" function that you pass the hash and the keys to, which traverses the hashes in order, breaking out when it can.
I would personally prefer one of the latter two approaches. Though I think it would be cute if the first was integrated into the Ruby language. (But monkey-patching is a bad idea. Don't do that. Particularly not to demonstrate what a cool hacker you are.)
Not that I would do it, but you can Monkeypatch in NilClass#[]:
> structure = { :a => { :b => 'foo' }}
#=> {:a=>{:b=>"foo"}}
> structure[:x][:y]
NoMethodError: undefined method `[]' for nil:NilClass
from (irb):2
from C:/Ruby/bin/irb:12:in `<main>'
> class NilClass; def [](*a); end; end
#=> nil
> structure[:x][:y]
#=> nil
> structure[:a][:y]
#=> nil
> structure[:a][:b]
#=> "foo"
Go with #DigitalRoss's answer. Yes, it's more typing, but that's because it's safer.
In my case, I needed a two-dimensional matrix where each cell is a list of items.
I found this technique which seems to work. It might work for the OP:
$all = Hash.new()
def $all.[](k)
v = fetch(k, nil)
return v if v
h = Hash.new()
def h.[](k2)
v = fetch(k2, nil)
return v if v
list = Array.new()
store(k2, list)
return list
end
store(k, h)
return h
end
$all['g1-a']['g2-a'] << '1'
$all['g1-a']['g2-a'] << '2'
$all['g1-a']['g2-a'] << '3'
$all['g1-a']['g2-b'] << '4'
$all['g1-b']['g2-a'] << '5'
$all['g1-b']['g2-c'] << '6'
$all.keys.each do |group1|
$all[group1].keys.each do |group2|
$all[group1][group2].each do |item|
puts "#{group1} #{group2} #{item}"
end
end
end
The output is:
$ ruby -v && ruby t.rb
ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-linux]
g1-a g2-a 1
g1-a g2-a 2
g1-a g2-a 3
g1-a g2-b 4
g1-b g2-a 5
g1-b g2-c 6
I am currently trying out this:
# --------------------------------------------------------------------
# System so that we chain methods together without worrying about nil
# values (a la Objective-c).
# Example:
# params[:foo].try?[:bar]
#
class Object
# Returns self, unless NilClass (see below)
def try?
self
end
end
class NilClass
class MethodMissingSink
include Singleton
def method_missing(meth, *args, &block)
end
end
def try?
MethodMissingSink.instance
end
end
I know the arguments against try, but it is useful when looking into things, like say, params.

Resources