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
Error Install Rubyracer with Error "Invalid Gem: Package Is Corrupt"
Ruby 2.7 Says Uri.Escape Is Obsolete, What Replaces It
Using Bsearch to Find Index for Inserting New Element into Sorted Array
Simplest Way to Send Raw Byte-Arrays Using Ruby's Tcpsocket-Class
Delete Contents of Array Based on a Set of Indexes
Best Way to Use Twitter Bootstrap Icons as Links in Ruby on Rails 3
Ruby: Converting from Float to Integer in Ruby Produces Strange Results
Is the .Each Iterator in Ruby Guaranteed to Give the Same Order on the Same Elements Every Time
Access Google Contacts API on Ruby
Gem Install Cannot Find a Header File
Scaffolding Activerecord: Two Columns of the Same Data Type
Rails Custom Validation Based on a Regex
Does Ruby 1.9.2 Have an Is_A? Function
How to Set a Hook to Run Code at the End of a Ruby Class Definition
Why Does a Rails App on Heroku Serve Assets via All.CSS and Locally via Individual Files
Install Cocoapods Failed on MAC
Use Ruby Array for a JavaScript Array in Erb. Escaping Quotes