Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

Ruby by default does not include the method [] for NilClass

For example, to check if foo["bar"] exists when foo may be nil, I have to do:

foo = something_that_may_or_may_not_return_nil
if foo && foo["bar"]
  # do something with foo["bar"] here
end

If I define this method:

class NilClass
  def [](arg)
    nil
  end
end

Something like that would make this possible, even if foo is nil:

if foo["bar"]
  # do something with foo["bar"]
end

Or even:

if foo["bar"]["baz"]
  # do something with foo["bar"]["baz"] here
end

Question: Is this a good idea or is there some reason ruby doesn't include this functionality by default?

share|improve this question
    
Nil, or undefined? If you haven't explicitly said foo = nil or otherwise referenced foo, and you call if foo["bar"], you're going to get an Undefined Method Error. Undefined stuff isn't automatically nil. –  KChaloux Oct 19 '12 at 19:41
    
That's true. I was assuming that foo has been set somewhere. If it's undefined, you still have the issue. But by defining [], you have one less issue. –  Silasj Oct 19 '12 at 19:43
    
It doesn't sound like an awful idea, but I'd be careful. Define it somewhere obvious, and make sure the behavior is consistent throughout. I never do much monkeypatching though, so I'm not speaking from experience. –  KChaloux Oct 19 '12 at 19:44
    
This smells like Perl autovivification. There's many fine caveats there, and you should probably find out which before changing a global behaviour of your language just in order to be lazy. –  Lars Viklund Oct 19 '12 at 19:52
1  
@Silasj My point was what if some other library should throw an exception in this case but no longer does because array access on nil now returns nil? Sometimes you want that NoMethodError. –  Rig Nov 2 '12 at 15:13
show 4 more comments

6 Answers

Nullable values must be addressed carefully, it's not the same a variable that always has a certain type than other which sometimes has this type, sometimes is null. Functional languages are usually the most careful on this regard and they have custom types to manage it (Maybe in Haskell, Option in Ocaml/F#/Scala, ...).

In Ruby I wouldn't extend the NilClass as you propose, it may lead to buggy code (note that you're modifying a basic object used by every library, it's a disaster waiting to happen). However, it's understandable that you want some pattern to succinctly interact with values that may be nil to avoid littering the code with conditionals. Personally I use the maybe proxy pattern proposed in Ick, explicit yet compact:

hash.maybe[key1].maybe[key2].maybe[key3]

Also using the slightly less verbose but more promiscuous block construction:

hash.maybe { |h| h[key1][key2][key3] }
share|improve this answer
    
The maybe syntax is not at all better than if foo && foo["bar"] && foo["bar"]["baz"] in my opinion. The only downside to extending NilClass as Silasj is suggesting is that it won't crash when maybe someone would expect it to. There is literally no other downside that I can think of. –  Jamon Holmgren Oct 19 '12 at 22:12
    
@JamonHolmgren: Well, yeah, it's a matter of taste, h.maybe[k1].maybe[k2] vs h && h[k1] && h[k1][k2]. The first looks more declarative to me. On the downsides, yeah, the one you mention is probably the more important, a library may depending on a NoMethod exception being raised in that situation. IMO it would also lead to buggy code, it may shadow problems (i.e. I miswrite a key but no exception is raised). –  tokland Oct 19 '12 at 22:22
    
You're right about the miswritten key. I didn't think of that. But even then, h.maybe[k1].maybe[k2] wouldn't solve that issue. –  Jamon Holmgren Oct 20 '12 at 19:31
    
@Jamon: of course, but the difference is you're being explicit by using "maybe". When you know the hash has those keys, you would simply write h[k1][k2][k3], if you patch NilClass you get always the "promiscous" behavior, you have zero choice. –  tokland Oct 20 '12 at 20:51
    
You do have a choice, albeit more verbose. I'd prefer the verbosity to only apply to the edge case of needing to check for nil. if h[k1].present? && h[k1][k2].present? && h[k1][k2][k3].present? (in Rails) is your choice in the rare instance where checking k1, k2 for presence is important. In nearly all cases I can think of, the presence of all is more important than the presence of individual ones in a hierarchy. –  Jamon Holmgren Oct 22 '12 at 21:38
show 1 more comment

In some ruby code that I've had to tweak, I have added isFalse or something of that nature to the nil object to make some parts be not unhappy with each other.

Open classes are not my favorite thing and making such a change to the nil class is not a small change. It is a significant change because it can change the entire functioning of the entire system.

You can do it. Make sure you regression test everything and document up front all changes to core objects so that other people don't become surprised when suddenly nil behaves in an unexpected way.

As to why that isn't default functionality - that is the realm of speculation into the mind of Matz.

share|improve this answer
    
It's not really too much of an issue, I just wondered why, with ruby's penchant for clean code, things like this: if foo && foo["bar"] && foo["bar"]["baz"] && foo["bar"]["baz"]["bang"] are necessary to find out if foo["bar"]["baz"]["bang"] exists when any of the preceding keys could be nil. –  Silasj Oct 19 '12 at 21:11
add comment

If this is for a specific condition, and I suspect it is, then delegate the behavior. Check that the object is defined, and then check if it is nil, and work accordingly.

It will preserve the expected behavior in core, and give you the specific changed behavior when you want it.

If later you need to modify the behavior to include False, you have one place to go to account for the needed change.

Changing core behavior for a specific need should always be considered carefully.

share|improve this answer
add comment

Why does something_that_may_or_may_not_return_nil have to return nil? And if you don't want to change that method, just wrap it in another method that returns an empty hash if the wrapped method returns nil.

As for nesting, you can use Hash's default_proc to return empty hashes when a key isn't found:

# self-referential, to provide arbitrary depth
default_proc = ->(h,k){ Hash.new(&default_proc) }
some_hash.default_proc = default_proc

The above lets you access deeply undefined entries without making any changes. But you also need to use empty? instead of treating the result as a simple boolean.

some_hash[:a][:b][:c].empty?

If you want a solution that will also store those new empty hashes into the parent, use this default_proc instead:

default_proc = ->(h,k){ h[k] = {}.tap{|newh| newh.default_proc = default_proc } }
# This will auto-vivify:
some_hash = {}
some_hash.default_proc = default_proc
some_hash[:a][:b][:c]
some_hash  # {:a=>{:b=>{:c=>{}}}}

Also, Rails provides the try method, that only calls the named method if the receiver isn't nil. Yeah, it's a lot uglier.

foo.try(:[], 'bar').try(:[], 'baz')

It might be better to wrap the arbitrarily-deep hash into a class that hides away the ugliness. For example, does the ['bar']['baz'] entry have some real-world meaning? If so, you can put a method on that class and have that method reach into that depth without the caller needing to worry about it.

share|improve this answer
add comment

I'd recommend you use Hash#fetch to access hash entries when you're unsure they'll be present. This way if the requested key is not found you'll get a KeyError which you can handle appropriately. When you blindly access nonexistent keys you get subtly confusing errors that wind up with you trying to act on unexpected nils in odd places elsewhere in your code.

> { a: { b: 1 } }[:a][:z].to_s
=> ""
> { a: { b: 1 } }.fetch(:z).fetch(:b).to_s
KeyError: key not found: :z

Aside: I developed my taste for Hash#fetch after watching Avdi Grimm's RubyTapas Episode #8. I pay money for that and so far it's worth it.

share|improve this answer
add comment

I wrote a gem designed for just this case. I came across this question when looking for a solution, so I thought I'd post here on what I ended up with.

Them gem is called Dottie and is available at https://github.com/nickpearson/dottie.

Here's a quick sample of how it's designed to be used:

car = {
  'type' => {
    'make' => 'Tesla',
    'model' => 'Model S'
  }
}

# normal Hash access assumes a certain structure
car['type']['make']     # => "Tesla"
car['specs']['mileage'] # => # undefined method `[]' for nil:NilClass

# Dottie lets you reach deep into a hash with no assumptions
d = Dottie(car)
d['type.make']     # => "Tesla"
d['specs.mileage'] # => nil

There's lots more documentation on the GitHub project page. Hope this helps someone.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.