How to Memoize a Method That May Return True, False, or Nil in Ruby

How can I memoize a method that may return true, false, or nil in Ruby?

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.

How can I save the value of an instance method?

I'm sure there's a more sophisticated answer out there, but for my app I just saved the value in an instance variable.

model

def method_true
#...
end

view

<% if @model.method_true.true? %>
<% @x = true %>
<% end %>

After that, you can use

<% if @x == true %>

instead of

<% if @model.method_true.true? %>

and the method won't have to recalculated. And if method_true isn't true, than @x will be nil, so the if? conditionals won't be executed.

How to execute a method only once in Ruby? Are there static variables?

There is an idiom in ruby: x ||= y.

def something
@something ||= calculate_something
end

private

def calculate_something
# some long process
end

But there is a problem with this idiom if your 'long running utility' may return a false value (false or nil), since the ||= operator will still cause the right side to be evaluated.
If you expect false values then use an additional variable, in a way similar to the proposed by DigitalRoss:

def something
return @something if @something_calculated
@something = calculate_something
@something_calculated = true
return @something
end

Don't try to save a line of code by setting the @something_calculated variable first, an then running calculate_something. If your calculate function raises an exception your function will always return nil and will never call the calculate again.

More generally, in Ruby you use instance variables. Note however, that they are visible in all the methods of given object - they are not local to the method.
If you need a variable shared by all instances, define the method in the class object, and in every instance call self.class.something

class User
def self.something
@something ||= calculate_something
end

def self.calculate_something
# ....
end

def something
self.class.something
end
end

Which Ruby memoize pattern does ActiveSupport::Memoizable refer to?

Here is the commit (and subsequent discussion) where Memoizable was deprecated: https://github.com/rails/rails/commit/36253916b0b788d6ded56669d37c96ed05c92c5c

The author advocates the @foo ||= ... approach and points to this commit as an example for migration: https://github.com/rails/rails/commit/f2c0fb32c0dce7f8da0ce446e2d2f0cba5fd44b3.

Edit:
Note that I don't necessarily interpret this change as meaning that all instances of memoize can or should be replaced w/ this pattern. I read it as meaning that Memoizable is no longer needed/wanted in the Rails code itself. As the comments point out, Memoizable is much more than just a wrapper around @foo ||= .... If you need those features, go ahead and use Memoizable, you'll just have to get it from somewhere other than ActiveSupport (I'm guessing someone will fork a gem version, if they haven't already).

Ruby random number generator is skewed when called from initializer

In ruby, all values other than nil and false are considered "truthy".

When you call foo ||= bar, bar will be evaluated if and only if foo is "falsey" - i.e. equal to nil or false. (Or if it's undefined!)

In your code, you have the following:

def switched?
@sw ||= [true, false].sample
end

So, the @sw variable will memoize the result of the method call only if [true, false].sample returns true!!

What this means, then, is that if you call switched? multiple times, you are giving the @sw variable "multiple attempts" to randomly choose true.

If you call switched? once, there's a 50% chance of it being true. Call it twice, and there's a 75% chance (as you observed). Call it 3 times, and there's a 87.5% chance. And so on.

In order to memoize a potentially false value, you need to be a little more explicit with the syntax - e.g.

def switched?
return @sw if defined?(@sw)
@sw = [true, false].sample
end

You can now call switched? multiple times, safely. It will remember its first result, even if false, and not re-calculate it.

Are ruby methods ending on '?' allowed to change stuff?

A predicate method with side-effects violates the Principle of Least Astonishment IMO. It may have "internal" side-effects, e.g. memoize some internal value, but it should be "externally" pure / referentially transparent.

If you subscribe to Bertrand Meyer's Command-Query Separation Principle, then predicate methods are Queries (which should not have side-effects). redirect_to is a Command (which has only side-effects and no return value). The two should not mix.

Using rails presenters - memoizable getting deprecated in 3.1 - use ||= instead?

The ||= method is great for things that return values that evaluate as true, but it doesn't work very well for things that don't. memoize does work around this by trapping these conditions and returning accordingly. You might take an approach like this if you want to accommodate nil:

def some_method
return @some_method if (instance_variable_defined?(:"@some_method"))

@some_method = begin
...
end
end

This just checks if the variable is defined, not if it is set, which is an important distinction in your case.

I'm not sure why you think it's being deprecated [Note from Michael, it's deprecated in 3.2, see note below]. The documentation indicates it's still current in 3.1. Sometimes implementations are marked as "deprecated" when they're being moved from one module to another, but the facility remains available.

What does ||= (or-equals) mean in Ruby?

This question has been discussed so often on the Ruby mailing-lists and Ruby blogs that there are now even threads on the Ruby mailing-list whose only purpose is to collect links to all the other threads on the Ruby mailing-list that discuss this issue.

Here's one: The definitive list of ||= (OR Equal) threads and pages

If you really want to know what is going on, take a look at Section 11.4.2.3 "Abbreviated assignments" of the Ruby Language Draft Specification.

As a first approximation,

a ||= b

is equivalent to

a || a = b

and not equivalent to

a = a || b

However, that is only a first approximation, especially if a is undefined. The semantics also differ depending on whether it is a simple variable assignment, a method assignment or an indexing assignment:

a    ||= b
a.c ||= b
a[c] ||= b

are all treated differently.



Related Topics



Leave a reply



Submit