Double-checking for nil and the Maybe Pattern

While listening to The Ruby Show episode #176 I heard about the ThoughtBot post about the Maybe Pattern as discussed in their “If you gaze into nil, nil gazes also into you” post.

It took me a minute to figure out what was going on. (Maybe I’m just getting slower in my old age.) Plagiarizing from their post, they included the code required to set up the Maybe Pattern and I’ll try to add my own comments in red so I feel like I’m adding something to this conversation:

This is modifying the base Object of which every Ruby object is
a child. Yes, that means even Nil gets an automatic maybe()
method stuck in it.

The idea is that if you think a method *might* return a value
then you ought to return your value with a .maybe() on it.
That accomplishes the "wrapping". You'll see that it takes
itself and returns an instance of Some with itself stuffed
into it. Thus at this point you really get a Some() object
back, not your original return value.
class Object
  def maybe
    Some.new(self)
  end
end

... Except for Nil. This one definitely is empty/blank/nothing
so we need to create an instance of Some that will throw
exceptions if you try to unwrap it.
class NilClass
  def maybe
    None.new
  end
end

And here is the Some object that wraps anything you give
it by storing it in its own @object attribute.
class Some
  def initialize(object)
    @object = object
  end

  This is how you "unwrap" an object. Pay close attention
that the None version of this will throw exceptions.
  def get
    @object
  end

  This is one way you can check to see if there might
be values.
  def present?
    true
  end

  And this is the other.
  def blank?
    false
  end
end

And back to some Magic Sauce. It creates a new error type,
Unwrapped, and will return it when you attempt to call get().
This is because as a consumer of the Some or None values
you ought to have checked present?() or blank?() first. That's 
right, consumers need to be sure that they're not just going
to use a nil value.
class None
  class Unwrapped < StandardError; end

  def get
    raise Unwrapped
  end

  def present?
    false
  end

  def blank?
    true
  end
end

OK, great. So how do you use this in practice?

When you return a value that is absolutely true or false, then do so. If you think your return value might be null then wrap it by tacking on a .maybe() call.

The consumer of these return values should always check for present?() or blank?(). If something is not present or is blank then you should no longer need to access the return value because, well, you got your answer.

However, if you need to use the return value for something (e.g. it might be a single object, or might be an array, or a Hash, or ...) then you need to check if it's present?() and/or not blank?(). If that's true then you can call the object's .get() method to grab that value and continue with your logic. Like:

accounts = bank(me).accounts
# accounts might be nil ... check present?()
if accounts.present?
  account_list = accounts.get
  # Now account_list definitely has something which isn't nil!
end

The main thought I had in all of this is that this will create tons of wrapper objects that could just add bloat to your application. Plus there's the extra evaluation involved. In my own code I almost always just use the Rails .blank?() method to check if things are there. That seems to handle nil, empty strings, and empty arrays just fine!

Leave a Reply