如何在Ruby中进行记忆化处理可能返回true、false或nil的方法?

20
显然,||= 不起作用。
def x?
  @x_query ||= expensive_way_to_calculate_x
end

因为如果它最终是falsenil,那么expensive_way_to_calculate_x将会一遍又一遍地运行。
目前我知道的最好方法是将该值放入一个Array中:
def x?
  return @x_query.first if @x_query.is_a?(Array)
  @x_query = [expensive_way_to_calculate_x]
  @x_query.first
end

有没有更常规或更有效的方法来做这件事?

更新 我意识到我想要记忆nil除了false之外 - 这可以追溯到https://rails.lighthouseapp.com/projects/8994/tickets/1830-railscachefetch-does-not-work-with-false-boolean-as-cached-value - 对于给出完全正确答案的Andrew Marshall,我深表歉意。


这就是nil和整个null概念的用途。 - user229044
我修改了我的问题,因为第一次没有做正确 - 我还想要memoize nil。 - Seamus Abshere
2个回答

30

需要明确检查@x_query的值是否为nil

def x?
  @x_query = expensive_way_to_calculate_x if @x_query.nil?
  @x_query
end
请注意,如果这不是实例变量,你还需要检查它是否已定义,因为所有实例变量默认为nil
考虑到您更新了@x_query的记忆化值可能为nil,您可以使用defined? 来避免所有实例变量默认为nil的问题:
def x?
  defined?(@x_query) or @x_query = expensive_way_to_calculate_x
  @x_query
end

请注意,这样做 a = 42 unless defined?(a) 不会按预期工作,因为一旦解析器遇到 a =a 就已经被定义了,在达到条件之前。但是,对于实例变量来说并非如此,因为它们默认为 nil 当解析器遇到 = 时不会定义它们。无论如何,我认为使用 orunless 的长块形式而不是带有 defined? 的单行 unless 可以保持一致性。


我会点赞这个答案,因为它回答了我第一个问题的版本,但请再看一下我的编辑--再次道歉。 - Seamus Abshere
@SeamusAbshere 我已经更新了我的答案以反映您的更新 :) - Andrew Marshall
3
a = 42 unless defined?(a) 这行代码会使 a 变成 nil,但是 @a = 42 unless defined?(@a) 则会使 @a 变成 42。因此,在这个例子中,并不绝对需要使用 or 语法。 - Andrew Grimm
@AndrewGrimm 嗯,我以为我已经验证了它对实例变量的行为是相同的,但看来你是正确的。我猜这是因为@a已经有一个值,即使它没有被定义。 - Andrew Marshall
@SeamusAbshere 我已经更新了我的答案,以反映实例变量对最后一位的影响。 - Andrew Marshall

28
为了处理 nil,使用 defined? 检查变量是否已被定义:
def x?
  return @x_query if defined? @x_query
  @x_query = expensive_way_to_calculate_x
end

defined? 如果变量未定义将返回nil,否则返回字符串"instance_variable"

irb(main):001:0> defined? @x
=> nil
irb(main):002:0> @x = 3
=> 3
irb(main):003:0> defined? @x
=> "instance-variable"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接