Memoizing nil and false

Posted 2006-12-03…

Here’s a common technique:

def foo
  @foo ||= compute_foo
end

For those that don’t know, this is an example of memoization; we only compute_foo if @foo hasn’t been assigned a value.

Well, that’s not strictly true. Let’s say that compute_foo returns nil or false, completely acceptable values for @foo. Let’s also say that compute_foo

take 2.3 minutes, chugs 150MB of RAM while running, and hits the network like a room full of Rails programmers at a conference (just for kicks— I have no idea what compute_foo does, but it’s resource intensive, obviously).

Using this traditional memoization technique, if compute_foo returns nil, we’ll be calling compute_foo unnecessarily every successive time we call foo. Oops.

Ruby’s pretty dynamic, so let’s make use of it:

def foo
  @foo = compute_foo unless instance_variables.include?('@foo')
  @foo
end

Let’s add Rails-style “expirable” memoization, too:

def foo(force_reload=false)
  unless force_reload || !instance_variables.include?('@foo')
    @foo = compute_foo
  end
  @foo
end

And there you have it.

Keep in mind what I’m talking about is definitely an edge case; in most situations, compute_foo won’t return nil or false, so this won’t come up. Also, if you find expirable memoization to be tedious to do by hand (like any sane person _does_), take a look at Marcel’s memoize in aws/s3 for a nifty approach.