Karol Galanciak - Ruby on Rails and Ember.js consultant

Ruby Memoization: ||= vs. Defined? Syntax

In the majority of the Rails applications or even Ruby gems you can find a lot of use cases where you need to memoize a result of some computation for performance benefits and to not compute it again if this result has already been computed. Seems like doing the assignment to some instance variable with ||= operator is the most commonly used solution for this purpose, e.g. @result ||= do_some_heavy_computation. However, there are some cases where it might not produce the expected outome and you should actually use defined? operator instead.

What Is ||= operator?

Let’s get back to the example from the introduction: @result ||= do_some_heavy_computation. What is this ||= operator and how does it work? It’s nothing more than a syntactic shortcut and it’s an equivalent of @result || @result = do_some_heavy_computation Edit: It’s very close to @result || @result = do_some_heavy_computation, but not exactly the same which translates to: “return the value of @result if the value is truthy or assign the result of do_some_heavy_computation to @result”. Clearly, the shortcut version looks more appealing. Keep in mind though that it’s not really about already assigning some value to the instance variable, but rather if the value of it is truthy or not. How do we check then if the instance variable has already been set knowing that referring to undefined instance variable will simply result in nil without any exceptions?

What Is defined? operator?

We can do that by using defined? opeator, which returns nil if its argument is not defined or, if it is defined, the description of that argument. Thanks to that behaviour, we can easily check if some instance variable has already been set or not:

1
2
3
4
defined?(@result) // => nil

@result = nil
defined?(@result) // => "instance-variable"

Memoization gotcha

Ok, we now understand the difference between ||= and defined? operators, why should we bother in the context of memoization?

Imagine that you have a following method in some object:

1
2
3
def heavy_computation_result
  @result ||= do_some_heavy_computation
end

and you are calling heavy_computation_result method multiple times to reuse the result of the computation. Certainly, this computation is heavy (as the name suggests) and ideally it should be computed only once for the performance reasons. What if this computation returns nil or false?

As @result ||= do_some_heavy_computation is nothing more than a shortcut of @result || @result = do_some_heavy_computation expression Edit: As @result ||= do_some_heavy_computation works in a pretty similar way to @result || @result = do_some_heavy_computation expression, the left side will be falsey in such case and the computation will be performed every time you call heavy_computation_result method making this syntax useless here!

For the proper memoization, this method should be rewritten using defined? operator:

1
2
3
4
def heavy_computation_result
  return @result if defined?(@result)
  @result = do_some_heavy_computation
end

Wrapping Up

Even though ||= operator is commonly used for memoization, it isn’t necessarily the best solution to this problem. It is certainly quite convenient to use, nevertheless, when there is a possiblity of having falsey values such as false and nil, it is much safer to use defined? operator instead.

Comments