How can I memoize a method that may return true, false, or nil in Ruby?
memoization, null, ruby
Solution
Explicitly check if the value of `@x_query` is `nil` instead:
def x?
@x_query = expensive_way_to_calculate_x if @x_query.nil?
@x_query
end
Note that if this wasn't an instance variable, you would have to check if it was defined also/instead, since all instance variables default to `nil`.
Given your update that `@x_query`'s memoized value can be `nil`, you can use `defined?` instead to get around the fact that all instance variables default to `nil`:
def x?
defined?(@x_query) or @x_query = expensive_way_to_calculate_x
@x_query
end
Note that doing something like `a = 42 unless defined?(a)` won't work as expected since once the parser hits `a =`, `a` is defined before it reaches the conditional. However, this isn't true with instance variables since they default to `nil` the parser doesn't define them when it hits `=`. Regardless, I think it's a good idiom to use `or` or `unless`'s long block form instead of a one-line `unless` with `defined?` to keep it consistent.
Problem
Obviously `||=` won't work ``` def x? @x_query ||= expensive_way_to_calculate_x end ``` because if it turns out to be `false` or `nil`, then `expensive_way_to_calculate_x` will get run over and over. Currently the best way I know is to put the value into an `Array`: ``` def x? return @x_query.first if @x_query.is_a?(Array) @x_query = [expensive_way_to_calculate_x] @x_query.first end ``` Is there a more conventional or efficient way of doing this? UPDATE I realized that I wanted to memoize `nil` in addition to `false` - this goes all the way back to https://rails.lighthouseapp.com/projects/8994/tickets/1830-railscachefetch-does-not-work-with-false-boolean-as-cached-value - my apologies to Andrew Marshall who gave an otherwise completely correct answer.