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.
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.
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.
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.
Why not just pass Array(single_or_multiple_items) to that operation which works on a collection? Built-in Array() takes care of it?
Matti: It’s a cool idea. It’s so cool that the function that returns a value should do it for you.
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.
Why not just pass Array(single_or_multiple_items) to that operation which works on a collection? Built-in Array() takes care of it?
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!
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!
Good call, yeah I messed that up.
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/
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?
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)
end
end
Calls to #or_else now work with or without the Maybe() contingency wrapper, which might be nice.
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?
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.
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.
Not a bad idea, that. Thanks!
The Maybe monad really is a collection, just one that has a max size of 1
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.
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.
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
That is depressingly similar to C++ functional programming libraries pre-C++0x.
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.
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?!”
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.
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/
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.
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.
Is it useless, stupid, … to define #empty? #blank? on NullObject, returning true?
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.
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 ofnil
needs to be changed?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.
You can use
nil
if you dare:Here is my take, with a Maybe monad in ruby https://gist.github.com/2225971 – what do you think?
It’s the most useful approach. I hope someday monads appears in Ruby. Thank you.
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.
imho… Just do it!
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.
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 theid
method.Thanks
When there is no parent class the parent is implicitly Object, which in 1.9 is a superset of BasicObject.
I found allowing the initalize of NullObject to accept a string which is used to provide a more descriptive
inspect
useful: https://gist.github.com/krisleech/5133588So 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.