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.
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
@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?
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:
defined?(@result) # => nil @result = nil defined?(@result) # => "instance-variable"
Ok, we now understand the difference between
defined? operators, why should we bother in the context of memoization?
Imagine that you have a following method in some object:
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
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
def heavy_computation_result return @result if defined?(@result) @result = do_some_heavy_computation end
||= 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
nil, it is much safer to use
defined? operator instead.