Null Objects and Falsiness

Thank you to Ben Hamill for sending me a question that prompted this post.

Checking for object presence

Very often in Ruby code, we would like to execute some action only if an object is present:

def slug(title)
  if title
    title.strip.downcase.tr_s('^[a-z0-9]', '-')
  end
end
slug(" Confident Code")
confident-code
h = {}
slug(h[:missing_key])
nil

Strictly speaking, we aren’t checking for object presence here. In Ruby there is almost always an object present, but the default marker for missing data is the special nil object—the one and only instance of NilClass.

h = {}
def noop; end
Expression Result
h[:missing] nil
noop nil
if (1==2) then 'wrong' end nil

What we are really checking for is the presence of either a “falsy” object (nil or false, most likely nil), or a “truthy” object (anything other than nil or false). So in effect this is an instance of typecasing.

Switching on object type is a smell in object-oriented languages. In general, we don’t want to ask an object what it is; we want to tell it what to do and let it figure out the best way to do it based on its type. This is the essence of polymorphism.

What we need is an object to represent the special case of “do nothing on missing value”. As it happens, there is a pattern for that: Null Object. Quoting Wikipedia: “a Null Object is an object with defined neutral (‘null’) behavior”.

Ruby does not have a built-in Null Object class (NilClass doesn’t qualify). Implementation of one is trivial, however:

class NullObject
  def method_missing(*args, &block)
    nil
  end
end

Instances of NullObject will respond to any message (method call) with a no-op.

no = NullObject.new
no.foobar
nil

A more useful (and common) form of Null Object returns self from every call.

class NullObject
  def method_missing(*args, &block)
    self
  end
end

This version makes it possible to nullify arbitrary chains of method calls:

NullObject.new.foobar.baz.buz
#<NullObject:0x7f97b56214f8>

A useful accompaniment to a Null Object class is a constructor method that converts nil values into null objects, and leaves other values as-is:

def Maybe(value)
  case value
  when nil then NullObject.new
  else value
  end
end

We can also define some common conversions a la NilClass:

class NullObject
  def to_a; []; end
  def to_s; ""; end
  def to_f; 0.0; end
  def to_i; 0; end
end

With these tools in hand we can rewrite our #slug method more cleanly, and, dare I say, more confidently:

def slug(title)
  Maybe(title).strip.downcase.tr('^[0-9a-z]', '-')
end
puts slug(" Exceptional Ruby ").to_s # => "exceptional-ruby" puts slug(nil).to_s # => "" 

In some cases we may want to call methods on other objects if the maybe-null object is not null. For this case, we can define the Ruby 1.9 / ActiveSupport #tap method as a no-op for NullObject:

class NullObject
  def tap; self; end
end
Maybe(obj).tap do
  puts "Object is present!"
end

The code in the #tap block will not be executed if the object is nil.

But is it falsey?

Still, even with a null object replacing nil there may be times when we want to check whether the expected object is present or not.

user = Maybe(User.find(123))
# ... if user
  "You are logged in as #{user.name}"
else
  "You are not logged in"
end
You are logged in as

Hm, that’s not what we wanted. There is no user, but the NullObject standing in for the user is “truthy”, because it’s not false or nil.

This is particularly surprising when we are branching based on the value of a predicate:

if user.subscribed?
  "Secret subscription-only stuff!"
end
Secret subscription-only stuff!

What’s going on here? The result of the call to #subscribed? is neither true nor false; it is the NullObject instance. Which, because of Ruby’s semantics, is truthy.

As it turns out, it is not possible in Ruby to make our own objects “falsey”. We can get close:

class NullObject
  # All Ruby objects define the #nil? predicate   def nil?; true; end
end
if !user.nil?
  "You are logged in as #{user.name}"
else
  "You are not logged in"
end
You are not logged in

In a program using ActiveSupport we might also define a few more common predicates:

class NullObject
  def present?; false; end
  def empty?; true; end
end

In Ruby 1.9 we can get even closer to the ideal of user-defined falsiness by implementing the ! (negation) operator:

class NullObject
  def !; true; end
end
if !!user
  "You are logged in as #{user.name}"
else
  "You are not logged in"
end

But still the goal of being able to treat our NullObject like a common nil eludes us.

We might also try basing NullObject on NilClass or FalseClass in order to inherit their falsiness. Unfortunately this too is impossible; NilClass and FalseClass are not allocatable, meaning it is not possible to create new objects of those classes (or any derivative of them).

class NullObject < NilClass
  # NilClass has no .new defined, so we have to recreate the default   # implementation   def self.new
    o = allocate
    o.initialize
    o
  end

  def method_missing(*args, &block)
    self
  end
end

no = NullObject.new # => raises a "no allocator defined" error

We could try another tack. We could define a function to “resolve” the null object back to a nil when needed:

def Value(object)
  case object
  when NullObject then nil
  else object
  end
end

Now we when we can wrap our maybe-null object with a Value() call to get the object we need:

if Value(user)
  "You are logged in as #{user.name}"
else
  "You are not logged in"
end

If we don’t mind extending core classes we could make this an instance method instead:

class Object
  def to_value
    self
  end
end

class NullObject
  def to_value
    nil
  end
end
if user.to_value
  "You are logged in as #{user.name}"
else
  "You are not logged in"
end
You are not logged in

Chasing after the wind

Let’s take a step back. Why do we care if the value is falsey? Because we want to handle that case differently. A lot of the time, the way we want to handle the absence of an object is to do nothing, and that’s the strategy that Null Object represents.

But here we want to do something when the value is missing; we want to return a different string. Maybe the problem isn’t really one of making NullObject act falsey. Maybe this isn’t the right scenario for a Null Object at all.

Let’s instead throw together a simple Presenter implementation:

require 'delegate'

class UserPresenter < SimpleDelegator
  def login_status
    "You are logged in as #{name}"
  end
end

class NilClassPresenter < SimpleDelegator
  def login_status
    "You are not logged in"
  end
end

def Present(object)
  presenter_class = Object.const_get("#{object.class.name}Presenter")
  presenter_class.new(object)
end

(In a real app the presenters would presumably have many more methods.) Let’s see how the code looks using presenters:

user = User.find(123)
# ... Present(user).login_status
You are not logged in

Ah, there we go. Back to good old polymorphism.

Conclusion

If we’re trying to coerce a homemade object into acting falsey, we may be chasing a vain ideal. With a little thought, it is almost always possible to transform code from typecasing conditionals to duck-typed polymorphic method calls. All we have to do is remember to represent the special cases as objects in their own right, whether that be a Null Object or something else.

42 comments

  1. One of the cleverest ways to exploit the idea behind the Null Object pattern is to devise operations which return lists (or Arrays) rather than single items.  If you have, say, a find which returns everything which matches a single id rather than a find which returns a single one, you can pass the result to any operation which works on a collection of them.  If there’s nothing in the collection, running the operation just works.  No special case code.

    1. Yes! This is a point I hit on in the “Confident Code” talk. I use jQuery as an example of iteration-style logic where everything is a collection, and point out that while singular operations are implicitly one-or-error, collection operations are inherently zero-or-more.

    2. Yes! This is a point I hit on in the “Confident Code” talk. I use jQuery as an example of iteration-style logic where everything is a collection, and point out that while singular operations are implicitly one-or-error, collection operations are inherently zero-or-more.

    3. Why not just pass Array(single_or_multiple_items) to that operation which works on a collection? Built-in Array() takes care of it?

      1. Matti: It’s a cool idea. It’s so cool that the function that returns a value should do it for you.

      2. The point was that methods which return 0 or more objects should always return an array instead of returning a single object or an array.  If you are making a call to something that explicity returns 0 (nil) or 1 object, then use, you should be the one wrapping it with [ ]’s.

    4. Why not just pass Array(single_or_multiple_items) to that operation which works on a collection? Built-in Array() takes care of it?

  2. Thanks for writing this post, Avdi. I have a small correction, which is near the heart of the whole falsey expectation:

    NullObject.new.foobar.baz.buz

    => and instance of NullObject, not nil.

    You talk about it later in the post, but I think it pays to be clear.I’m going to noddle on this some more and likely come comment more later on. Thanks again!

  3. Thanks for writing this post, Avdi. I have a small correction, which is near the heart of the whole falsey expectation:

    NullObject.new.foobar.baz.buz

    => and instance of NullObject, not nil.

    You talk about it later in the post, but I think it pays to be clear.I’m going to noddle on this some more and likely come comment more later on. Thanks again!

    1. I ended up having longer thoughts than I felt would work in a comment, so I blogged. @avdi:disqus Whatchya think? http://garbled.benhamill.com/2011/06/falsiness-and-null-objects/

  4. If you wanted to monkey patch Object you could also add the method #or_else and then you’d have something like what scala has.

    greeting = Maybe(current_user).to_greeting.or_else(“Welcome, Anonymous!”)

    greeting = Maybe(current_user).to_greeting.or_else { link_to(‘Sign Up’, signup_path) }

    Or something like that. Thoughts?

    1. Bit late to this thread…

      If you’re going to patch Object, why not move the entirety of #or_else into Object:

      class Object   

      def or_else(*args, &block)     

      return self unless self.nil?      
      
      yield if block      
      
      return args[0] if args.length == 1      
      
      return Array(args)    
      

      end

      end

      Calls to #or_else now work with or without the Maybe() contingency wrapper, which might be nice.

  5. If you wanted to monkey patch Object you could also add the method #or_else and then you’d have something like what scala has.

    greeting = Maybe(current_user).to_greeting.or_else(“Welcome, Anonymous!”)

    greeting = Maybe(current_user).to_greeting.or_else { link_to(‘Sign Up’, signup_path) }

    Or something like that. Thoughts?

    1. Very Smalltalk-ish 🙂 I often debate using methods like that in my code… on the one hand, it’s taking OO to its logical end. On the other hand it doesn’t feel like idiomatic Ruby, and when I get too far from the idiom I feel like I should either bite the bullet and switch languages, or cut more with the grain of the language.

      In any case it’s great food for thought.

  6. A minor suggestion, since you’re already using the method name “Maybe” to wrap possibly nil values, why not use the name “Just” instead of “Value”?  Granted, that’s a Haskell convention for the maybe monad, but there’s no harm in using the same names.

    I really do wish there were a way to allow new “falsey” values in Ruby.

  7. data Option a = Just a | Nothing — Haskell
    datatype ‘a option = none | some of ‘a (* SML *)

    Catch up guys.

    PS: No, that is not the essence of polymorphism, not even approximately.

    1. Yes, I’m quite familiar with this pattern in Haskell. Your comment adds heat and no light. Give me one good reason not to delete it.

  8. This is more cute than anything else, but if you want user-defined truthiness/falsiness you can define your own if/elsif/else control structures in pure ruby:

        if_(0) {
          puts “truthiness”
        }.
        else {
          puts “falsiness”
        }

        #=> falsiness

    see here: https://github.com/banister/custom_boolean

    hehe
           

  9. Since NullObjects are stateless, I typically create only one of them.

    NULL_OBJECT = NullObject.new

    and then use NULL_OBJECT rather than NullObject.new in the rest of my code.  Add appropriate layers of convenience as needed.

    1. That’s definitely the more memory-efficient way to do it.

      Something I demonstrate in the “Confident Code” talk is that because, unlike nil, NullObjects don’t HAVE to be stateless, you can create a version that remembers where it was instantiated. Not as efficient, but it answers once and for all the question “where the hell did that null come from?!”

  10. Came back to mention  @raganwald:twitter ‘s #and#and: https://github.com/raganwald/andand/blob/master/README.textile

    It’s right up this alley, dealing with nil in a clever way.

    Reginald Braithwaite’s blog (@raganwald) is a goldmine for this sort of stuff, btw.

    1. Reg is a friend and a former coworker, and I am honored to be quoted in the README for his followup to andand: http://ick.rubyforge.org/

  11. If we override NullObject#to_s, when inspected, we get an empty string. It’s not a big deal, but can be disturbing.

    I’ve tried to reimplement #inspect, but then I discovered that it’s not as simple as “#”. As a matter of fact the part after the colon is a much more internal identification of the object.

    I’ve ended up not overriding #to_s.

    Anyway, Thanks for this great article and your constant focus on good object orientation.

    1. At least with ruby 2.0.0p353, #inspect doesn’t delegate to #to_s, #to_s delegates to #inspect. I can’t imagine this would be any different in earlier rubies either.

  12. Shouldn’t code like Object.const_get(“#{object.class.name}Presenter”) be considered a switching on object type? Also, this makes it impossible to search where UserPresenter is being used.

  13. It is pity that nil object does not qualify, i thought that is what it was for.  Now it seems that there will be two null objects…  Maybe Ruby specification of nil needs to be changed?

    1. Null Object is a pattern for special cases; having it be the default behavior of the standard void/nil/null object leads to more trouble than it’s worth. Ruby made the right decision, although I wish the stdlib packaged a Null Object class out of the box.

    2. You can use nil if you dare:

      class Object
        def method_missing (name, *args)
           nil
        end
      end
      
    1. It’s the most useful approach. I hope someday monads appears in Ruby. Thank you.

  14. Hi Avdi,

    Thanks again for this post I’m coming back to regularly.

    Here a use case I’d like you opinion about.

    In a Rails 2.3 app, I have a model with a has_many relation, say Post has_many Comments. In a situation, I want to get the id of the first comment of a post, if it exists.

    I can do first_comment_id = post.comment.first.try(:id) but its baaaad!

    I wanted to use a NullObject (with or without Maybe) but then i get the “object_id” of the null object.

    Does it seem OK to explicitly define the instance method “id” on NullObject to return self?

    I guess that’s a situation I’ll get only in a Rails 2.3 app because it is aliasing Object#id with Object#object_id + a warning.

    Thanks for your input.

    1. If you descend your null object from BasicObject (part of core in Ruby 1.9 and IIRC a version is included in ActiveSupport otherwise) you shouldn’t have to worry about #id already being defined.

      1. My NullObject class had no parent class. If I descend it from ActiveSupport::BasicObject (I use Ruby 1.8 here, with Rails 2.3), you’re right, I don’t need to redefine the id method.

        Thanks

  15. So to avoid a typecase, you add a typecase in the construction of Maybe. And when a NullObject is unexpected, you’re just left wondering what happened, instead of getting an error.

    Maybe an explanation of why that’s a code smell, and whether this case actually stinks at all is in order.

Comments are closed.