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

前端 未结 2 946
醉酒成梦
醉酒成梦 2020-12-16 13:49

Obviously ||= won\'t work

def x?
  @x_query ||= expensive_way_to_calculate_x
end

because if it turns out to be false

相关标签:
2条回答
  • 2020-12-16 14:16

    To account for nil, use defined? to see if the variable has been defined:

    def x?
      return @x_query if defined? @x_query
      @x_query = expensive_way_to_calculate_x
    end
    

    defined? will return nil if the variable hasn't been defined, or the string "instance_variable" otherwise:

    irb(main):001:0> defined? @x
    => nil
    irb(main):002:0> @x = 3
    => 3
    irb(main):003:0> defined? @x
    => "instance-variable"
    
    0 讨论(0)
  • 2020-12-16 14:26

    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.

    0 讨论(0)
提交回复
热议问题