Suspicions of nil

I'm feeling suspicious of nil.

What is nil?

In a recent newsletter I pondered true and false and suggested that thinking of them as normal, everyday objects could expand your ideas about OOP.  Today I'll continue with this theme by considering nil.

What _is_ nil?

A straightforward, very concrete definition might be that it's an instance of NilClass and a specialized kind of Ruby singleton that responds to 70+ public/protected methods.

Let's have a look.

> ruby -v
ruby 2.2.0preview2 (2014-11-28 trunk 48628)

> pry
[1]> nil.class
=> NilClass
[2]> nil.singleton_class
=> NilClass

In Ruby, nil (and also true and false) are defined in a slightly special way, such that their class and their singleton class are the same.  As someone who's not all that caught up in the gory internal details, I generally ignore this.

[3]> NilClass.instance_methods.count
=> 73

So, nil responds to 73 methods.  What are they?

[4]> NilClass.instance_methods.sort
=> [
 :!,
 :!=,
 :!~,
 :&,
 :<=>,
 :==,
 :===,
 :=~,
 :^,
 :__binding__,
 :__id__,
 :__send__,
 :class,
 :clone,
 :define_singleton_method,
 :display,
 :dup,
 :enum_for,
 :eql?,
 :equal?,
 :extend,
 :freeze,
 :frozen?,
 :hash,
 :inspect,
 :instance_eval,
 :instance_exec,
 :instance_of?,
 :instance_variable_defined?,
 :instance_variable_get,
 :instance_variable_set,
 :instance_variables,
 :is_a?,
 :itself,
 :kind_of?,
 :method,
 :methods,
 :nil?,
 :object_id,
 :pretty_inspect,
 :pretty_print,
 :pretty_print_cycle,
 :pretty_print_inspect,
 :pretty_print_instance_variables,
 :private_methods,
 :protected_methods,
 :pry,
 :public_method,
 :public_methods,
 :public_send,
 :rationalize,
 :remove_instance_variable,
 :respond_to?,
 :send,
 :singleton_class,
 :singleton_method,
 :singleton_methods,
 :taint,
 :tainted?,
 :tap,
 :to_a,
 :to_c,
 :to_enum,
 :to_f,
 :to_h,
 :to_i,
 :to_r,
 :to_s,
 :trust,
 :untaint,
 :untrust,
 :untrusted?,
 :|]

Many of these methods were inherited from Object.  Let's reduce this list to just those methods defined in NilClass.

[5]> NilClass.instance_methods(false).count
=> 14

Okay, what are they?

[6]> NilClass.instance_methods(false).sort
=> [
 :&,
 :^,
 :inspect,
 :nil?,
 :pretty_print_cycle,
 :rationalize,
 :to_a,
 :to_c,
 :to_f,
 :to_h,
 :to_i,
 :to_r,
 :to_s,
 :|]

So, nil knows some boolean operations and it can convert itself into a few other things. As nil is already defined when I start Ruby, clearly Ruby itself created this instance of NilClass and stored it in nil for my use.

Using nil to mean 'no result'

So far this has been fairly mundane.  NilClass is clearly a class and nil is the one and only instance of that class; there's no magic here.  Even if you thought of nil as 'special' when you started this post, by now you should be quite disabused of that notion.  nil is a regular old object that responds to its own set of messages, just like any other.

However, despite this apparent normalcy, we create problems for ourselves because it's so easy to treat nil as something other than a straightforward object.  For example, it's common to use nil to signal absence of a result to the senders of messages.  As a matter of fact, one might defensibly argue that this is part of nil's purpose.

Have a look at this class:

class Animal
  attr_reader :name

  # Obviously fake-o, but a useful
  # simplification for this example.
  def self.find(id)
    if id == ''
      nil
    else
      new(id)
    end
  end

  def initialize(name)
    @name = name
  end
end

Animal.find('pig')
=> #<Animal:0x007fafca029a28 @name="pig">

Animal.find('')
=> nil

Notice that Animal.find normally returns an instance of Animal, but when it receives an empty string for id it returns nil.  This implementation means that if an innocent bystander gets passed a list of ids...

ids = ['pig', '', 'sheep']

which they then use to look up Animals...

animals = ids.map {|id| Animal.find(id)}

when they ask each animal for its name they encounter this problem:

animals.each {|animal| puts animal.name}
=> pig
=> NoMethodError: undefined method `name' for nil:NilClass

Kaboom.

The proximate cause of this error is clearly that nil doesn't understand name, but what really happened?

The underlying OO problem is that Animal.find returns instances of two different types (Animal or NilClass) and these types conform to different API's.

Let me repeat that.

The underlying problem is that Animal.find returns objects which conform to different API's.

This has deep consequences.  Senders of Animal.find:

  • are forced to examine the type of the result in order to know how it behaves, and then
  • must write code that in effect says, "Well, if I get back this type of thing then I'll supply the proper behavior, but if I get back anything else I'll just expect whatever I got back to respond to name.

Senders of Animal.find incur a deep and pervasive burden when they cannot trust returned objects to respond to a common set of messages.

Which leads us to...

Who should supply the correct behavior?

If you plan to send name to whatever you get back, and you get back nil instead of an Animal, you are forced to explicitly check for nil and supply the correct behavior yourself.

There are many ways to do this and, frankly, most don't seem horrible.  One very straightforward option is to send the nil? message to the value held in animal and, based on the result, choose between returning the correct response or sending the name message.  Here's an implementation that returns the string 'no animal' when the result is nil:

animals.each {|animal| 
  puts animal.nil? ? 'no animal' : animal.name}
=>
pig
no animal
sheep

Alternatively, you can be less verbose and rely on nil's falsy-ness.  The following implementation returns an empty string when there's no animal:

animals.each {|animal| puts animal && animal.name}
=>
pig

sheep

RubyOnRails mitigates the repetition in the previous example by monkey patching Object and NilClass with the try method.  If you're using Rails (or if you were to add the patch yourself) you can reduce the last example to:

animals.each {|animal| puts animal.try(:name)}
=>
pig

sheep

But try, handy as it may be, is an enabler.  Using it reflexively encourages a kind of thinking that is detrimental to your understanding of objects.

Please understand, I'm not suggesting that you never use try or that there's never a good reason to return nil, I just want you to be conscious of the bargain you're making when you do.  Workarounds like try make it easy to ignore the fact that you're invoking methods that return untrustworthy results and that your code is forcing the senders of messages to check the type of the return and then supply missing behavior.

If you send Animal.find from more than one place in your application and you expect to be able to send name to whatever you get back, you are now doomed to duplicate this missing behavior in every one of those places.  No amount of making the code that supplies the missing behavior 'easy to write' addresses the underlying problem.

To my mind, the following variants are dang near identical and equally troublesome.  If you object to the last, you should be bothered by the first.

animals.each {|animal| puts animal.try(:name)}

animals.each {|animal| 
  puts animal.nil? ? '' : animal.name}

animals.each {|animal| 
  puts animal == nil ? '' : animal.name}

animals.each {|animal| 
  puts animal.is_a?(NilClass) ? '' : animal.name}

Each example above puts the burden of knowing how to handle two unrelated return types on senders of Animal.find.

We've come to blithely accept this when one of the two return types is NilClass, but I'll bet you'd complain mightily about an API that most times returned an Animal but, occasionally, if it couldn't find a suitable Animal, chose instead to return a Carrot.  Carrots are orange and crunchy and my Guinea Pigs love them but they in no way behave like animals.  A substitution of this sort has downstream consequences for everyone.

In flexible, forgiving, easily maintainable OO apps the objects returned from message sends conform to a common API; they behave exactly as you expect.  It's one thing to return nil to signal 'no result' and it's quite another to return nil to callers who expect it to respond to messages it can't possibly understand.

I completely concede that returning nil can be handy and agree, in advance, that it's sometimes exactly right.  I'm not suggesting that you never do this; I do it myself.  However, even when nil is the right thing to return, we want to avoid forcing many places in our application to supply the same 'alternative name' behavior.

Something must be done.  :-)

That thing involves the Null Object Pattern, which will be the subject of my next post.

News: 99 Bottles of OOP in JS, PHP, and Ruby!

The 2nd Edition of 99 Bottles of OOP has been released!

The 2nd Edition contains 3 new chapters and is about 50% longer than the 1st. Also, because 99 Bottles of OOP is about object-oriented design in general rather than any specific language, this time around we created separate books that are technically identical, but use different programming languages for the examples.

99 Bottles of OOP is currently available in Ruby, JavaScript, and PHP versions, and beer and milk beverages. It's delivered in epub, kepub, mobi and pdf formats. This results in six different books and (3x2x4) 24 possible downloads; all unique, yet still the same. One purchase gives you rights to download any or all.

Posted on December 22, 2014 .