Memoization can be a useful tool in programming. The idea is to store the results of an expensive computation and then reference the stored result.
In Ruby 📖, you might see the following:
def memozied_result @memoized_result ||= expensive_calculation_for_results end
||= operator does a bit of work, so let’s expand that to it’s logical equivalent:
def memozied_result return @memoized_result if @memoized_result @memoized_result = expensive_calculation_for_results end
Consider what happens if
expensive_calculation_for_results returns something “falsey” (e.g.
false); you’ll always call
expensive_calculation_for_results. Which defeats the purpose of memoization.
Before we go further, we need to understand an implementation detail of Ruby’s instance variables.
Implementation Detail of Ruby’s Instance Variables
When we reference an undeclared instance variable, the returned value of that instance variable is
nil. When we set an instance variable to
nil, the returned value of that instance variable is
Let’s fire up an IRB instance:
❯ irb irb(main):001:0> @memoized_result => nil irb(main):002:0> @memoized_result = nil => nil irb(main):003:0> @memoized_result => nil
In other words, we don’t know if we’ve set
@memoized_result or not. We need something different.
defined? method returns the definition type of the object passed to the
defined? method. It returns
nil if it is not defined.
❯ irb irb(main):001:0> defined?(@memoized_result) => nil irb(main):002:0> @memoized_results = nil => nil irb(main):003:0> defined?(@memoized_result) => "instance-variable"
The above example demonstrates that we can use
defined? to help with our memoization quandry.
Let’s rewrite our method to use memoization that copes with the expensive computation returning
def memozied_result return @memoized_result if defined?(@memoized_result) @memoized_result = expensive_calculation_for_results end
The above is not as elegant as the terse
@memoized_result ||= expensive_calculation_for_results.
A Rails Sidebar
Let’s expand just a bit and look at this through the lens of ActiveSupport’s
#present? method; a helpful method that Ruby on Rails 📖 provides.
In the below example, I’m running Bundler’s console command with a Gemfile that requires ActiveSupport.
❯ bin/console irb(main):001:0> nil.present? => false irb(main):002:0> @memoized_result.present? => false irb(main):003:0> defined?(@memoized_result) => nil irb(main):004:0> @memoized_result = nil => nil irb(main):005:0> @memoized_result.present? => false irb(main):006:0> defined?(@memoized_result) => "instance-variable" irb(main):007:0>
#present? method does not check if the receiver is defined. It checks the value of the receiver. So we can’t rely on
#present? when we really want
Memoization is intended to help cache potentially expensive computations. And Ruby’s elegant idioms can lead towards thinking something is memoized, when in fact we have to guard against the dread conceptual Null Pointer.