What does ||= (or-equals) mean in Ruby?
以下代码在Ruby中的含义是什么?
1 | ||= |
它的语法是否有任何意义或原因?
1 2 3 4 5 6 7 | a ||= nil # => nil a ||= 0 # => 0 a ||= 2 # => 0 foo = false # => false foo ||= true # => true foo ||= false # => true |
令人困惑的是,它看起来与其他赋值运算符(例如
-
a += b 转换为a = a + b -
a ||= b 大致翻译为a || a = b
它是
进一步阅读:
- http://www.rubyinside.com/what-rubys-double-pipe-or-equals-really-does-5488.html
这个问题经常在Ruby邮件列表和Ruby博客上讨论过,现在甚至在Ruby邮件列表上都有线程,其唯一的目的是收集Ruby邮件列表上讨论这个问题的所有其他线程的链接。 。
这是一个:|| =(OR Equal)线程和页面的权威列表
如果您真的想知道发生了什么,请查看Ruby语言草案规范的第11.4.2.3节"缩写分配"。
作为第一个近似值,
1 | a ||= b |
相当于
1 | a || a = b |
并不等同于
1 | a = a || b |
但是,这只是第一次近似,特别是如果
1 2 3 | a ||= b a.c ||= b a[c] ||= b |
都被区别对待。
简洁而完整的答案
1 | a ||= b |
评估方式与以下每行相同
1 2 3 | a || a = b a ? a : a = b if a then a else a = b end |
-
另一方面,
1 | a = a || b |
评估方式与以下每行相同
1 2 | a = a ? a : b if a then a = a else a = b end |
-
编辑:正如AJedi32在评论中指出的那样,这只适用于:1。a是一个已定义的变量。 2.评估一次和两次不会导致程序或系统状态的差异。
简而言之,
基本上,
如果
将
它意味着或等于。它检查左侧的值是否已定义,然后使用它。如果不是,请使用右侧的值。您可以在Rails中使用它来缓存模型中的实例变量。
一个快速的基于Rails的示例,我们创建一个函数来获取当前登录的用户:
1 2 3 4 5 6 7 | class User > ActiveRecord::Base def current_user @current_user ||= User.find_by_id(session[:user_id]) end end |
它检查是否设置了@current_user实例变量。如果是,它将返回它,从而保存数据库调用。如果没有设置,我们进行调用,然后将@current_user变量设置为该调用。这是一种非常简单的缓存技术,但是当您在应用程序中多次获取相同的实例变量时非常适合。
1 | x ||= y |
是
1 | x || x = y |
"如果x为false或未定义,则x指向y"
确切地说,
其他人经常试图通过说
-
a ||= b ?a || a = b ?当
a 是未定义的局部变量时,这些语句的行为会有所不同。在这种情况下,a ||= b 将a 设置为b (并评估为b ),而a || a = b 将提高NameError: undefined local variable or method 'a' for main:Object 。 -
a ||= b ?a = a || b ?通常假定这些语句的等效性,因为对于其他缩写赋值运算符(即
+= ,-= ,*= ,/= ,%= ,**= ,&= ),类似的等价性是正确的,|= ,^= ,<<= 和>>= )。但是,对于||= ,当a= 是对象上的方法且a 是真实的时,这些语句的行为可能会有所不同。在这种情况下,a ||= b 将不执行任何操作(除了计算a ),而a = a || b 将在a 的接收器上调用a=(a) 。正如其他人所指出的,当调用a=a 具有副作用时,例如向哈希添加键,这可能会有所不同。 -
a ||= b ?a = b unless a ?这些陈述的行为仅在它们评估时
a 是真实的时候有所不同。在这种情况下,a = b unless a 将评估为nil (尽管a 仍未按预期设置),而a ||= b 将评估为a 。 -
a ||= b ?defined?(a) ? (a || a = b) : (a = b) ????仍然没有。当存在
method_missing 方法时,这些语句可能会有所不同,该方法返回a 的真值。在这种情况下,a ||= b 将评估为method_missing 返回的任何值,而不是尝试设置a ,而defined?(a) ? (a || a = b) : (a = b) 将a 设置为b 并计算为b 。
好的,好吧,
好吧,假设我没有忽略任何东西,我相信
1 2 3 4 | begin a = nil if false a || a = b end |
坚持,稍等!这不是第一个带有noop的例子吗?嗯,不太好。还记得我之前说过,当
假设
那么,
当评估某个值未导致
现在假设
然后
因为它首先尝试评估导致
ror应用程序中使用的最佳示例是:
1 2 3 4 5 6 7 | #To get currently logged in iser def current_user @current_user ||= User.find_by_id(session[:user_id]) end # Make current_user available in templates as a helper helper_method :current_user |
其中,当且仅当
x = y
end
除非x有一个值(它不是nil或false),否则将其设置为y
相当于
1 | a ||= b |
相当于
1 | a || a = b |
并不是
1 | a = a || b |
因为您使用默认值定义哈希的情况(哈希将返回任何未定义键的默认值)
1 | a = Hash.new(true) #Which is: {} |
如果您使用:
1 | a[10] ||= 10 #same as a[10] || a[10] = 10 |
a仍然是:
1 | {} |
但是当你这样写的时候:
1 | a[10] = a[10] || 10 |
变为:
1 | {10 => true} |
因为你已经在key
这就像懒惰的实例化。
如果已定义变量,则将采用该值而不是再次创建该值。
这是默认的赋值表示法
例如:x || = 1
这将检查x是否为零。如果x确实为零,则它将为其分配新值(在我们的示例中为1)
更明确:
如果x == nil
x = 1
结束
还请记住
|| =是一个条件赋值运算符
1 | x ||= y |
相当于
1 | x = x || y |
或者
1 2 3 4 5 | if defined?(x) and x x = x else x = y end |
作为一种常见的误解,
但这是一个棘手的案例。如果未定义
a || = b
表示'a'中是否存在任何值,并且您不想改变它继续使用该值,否则如果'a'没有任何值,则使用'b'的值。
简单的单词,如果左侧不为空,则指向现有值,否则指向右侧的值。
1 2 | b = 5 a ||= b |
这意味着:
1 | a = a || b |
这将是
1 | a = nil || 5 |
所以最后
1 | a = 5 |
现在,如果再次调用它:
1 2 3 4 5 6 | a ||= b a = a || b a = 5 || 5 a = 5 b = 6 |
现在,如果再次调用它:
1 2 3 4 | a ||= b a = a || b a = 5 || 6 a = 5 |
如果观察到,
它是一个Memoization模式,在Ruby中用于加速访问器。
1 2 3 | def users @users ||= User.all end |
这基本上转化为:
1 | @users = @users || User.all |
因此,您第一次调用此方法时将调用数据库。
将来对此方法的调用只返回
1 2 3 4 | irb(main):001:0> a = 1 => 1 irb(main):002:0> a ||= 2 => 1 |
因为
1 2 3 4 | irb(main):003:0> a = nil => nil irb(main):004:0> a ||= 2 => 2 |
因为
它基本上用作
第一个例子:
1 | x ||= 10 |
第二个例子:
1 2 | x = 20 x ||= 10 |
在第一个示例中,
但是所有3个选项都显示相同的性能吗?用Ruby 2.5.1这个
1 2 3 4 5 6 7 8 9 10 11 12 | 1000000.times do a ||= 1 a ||= 1 a ||= 1 a ||= 1 a ||= 1 a ||= 1 a ||= 1 a ||= 1 a ||= 1 a ||= 1 end |
在我的电脑上需要0.099秒,而
1 2 3 4 5 6 7 8 9 10 11 12 | 1000000.times do a = 1 unless a a = 1 unless a a = 1 unless a a = 1 unless a a = 1 unless a a = 1 unless a a = 1 unless a a = 1 unless a a = 1 unless a a = 1 unless a end |
需要0.062秒。这几乎要快40%。
然后我们还有:
1 2 3 4 5 6 7 8 9 10 11 12 | 1000000.times do a = 1 if a.nil? a = 1 if a.nil? a = 1 if a.nil? a = 1 if a.nil? a = 1 if a.nil? a = 1 if a.nil? a = 1 if a.nil? a = 1 if a.nil? a = 1 if a.nil? a = 1 if a.nil? end |
这需要0.166秒。
并不是说这会对整体性能产生重大影响,但如果您确实需要最后一点优化,那么请考虑这个结果。
顺便说一句:
注1:多次重复分配线的原因是为了减少测量时间的循环开销。
注2:如果我在每次分配之前