ruby运算符|| =智能吗?

我有一个关于ruby中的|| =语句的问题,这对我来说特别有意义,因为我正在使用它来写入memcache。 我想知道的是,|| =先检查接收器,看它是否在调用该setter之前设置,或者它实际上是x = x || y的别名 x = x || y

在普通变量的情况下,这并不重要,但使用如下:

 CACHE[:some_key] ||= "Some String" 

可能会做一个比简单变量集更昂贵的memcache写入。 奇怪的是,我在ruby api中找不到关于|| =的任何内容,所以我自己无法回答这个问题。

我当然知道:

 CACHE[:some_key] = "Some String" if CACHE[:some_key].nil? 

会实现这个,我只是在寻找最简洁的语法。

这非常容易测试:

 class MyCache def initialize @hash = {} end def []=(key, value) puts "Cache key '#{key}' written" @hash[key] = value end def [](key) puts "Cache key '#{key}' read" @hash[key] end end 

现在只需尝试||=语法:

 cache = MyCache.new cache["my key"] ||= "my value" # cache value was nil (unset) # Cache key 'my key' read # Cache key 'my key' written cache["my key"] ||= "my value" # cache value is already set # Cache key 'my key' read 

因此,我们可以得出结论,如果缓存密钥已经存在,则不会进行任何分配。

Rubyspec的以下摘录表明这是设计使然不应该依赖于Ruby实现:

 describe "Conditional operator assignment 'obj.meth op= expr'" do # ... it "may not assign at all, depending on the truthiness of lhs" do m = mock("object") m.should_receive(:foo).and_return(:truthy) m.should_not_receive(:foo=) m.foo ||= 42 m.should_receive(:bar).and_return(false) m.should_not_receive(:bar=) m.bar &&= 42 end # ... end 

在同一个文件中, [][]=有一个类似的规范,要求相同的行为。

虽然Rubyspec仍在进行中,但很明显主要的Ruby实现项目打算遵守它。

根据ISO规范草案的 §11.3.1.2.2,

 CACHE[:some_key] ||= "Some String" 

扩展到

 o = CACHE *l = :some_key v = o.[](*l) w = "Some String" x = v || w l << x o.[]=(*l) x 

或者,在更一般的情况下

 primary_expression[indexing_argument_list] ω= expression 

(我在这里使用ω表示任何运算符,所以它可能是||=+=*=>>=%= ,...)

扩展到:

 o = primary_expression *l = indexing_argument_list v = o.[](*l) w = expression x = v ω w l << x o.[]=(*l) x 

因此,根据规范, []=始终被调用。 但实际情况并非如此(我测试了MRI,YARV,Rubinius,JRuby和IronRuby):

 def (h = {}).[]=(k, v) p "Setting #{k} to #{v}"; super end h[:key] ||= :value # => :value # "Setting key to value" h[:key] ||= :value # => :value 

因此,显然规范是错误的,或者所有五个当前发布的实现都是错误的。 并且由于规范的目的是描述现有实现的行为,因此显然规范必定是错误的。

通常,作为第一近似值

 a ||= b 

扩展到

 a || a = b 

然而,涉及各种各样的问题,例如, a是否未定义, a是简单变量还是更复杂的表达式,如foo[bar]foo.bar等。

另请参阅此问题的其他一些实例,这些实例已经在StackOverflow上得到了解答(例如, 这一个 )。 此外,在ruby-talk邮件列表上已经多次讨论过这个问题,现在有讨论主题,其唯一目的是总结其他讨论主题。 (虽然请注意,该列表还远未完成。)

这是另一个与其他答案略有不同的演示,因为它明确显示何时写入Hash:

 class MyHash < Hash def []=(key, value) puts "Setting #{key} = #{value}" super(key, value) end end >> h = MyHash.new => {} >> h[:foo] = :bar Setting foo = bar => :bar >> h[:bar] ||= :baz Setting bar = baz => :baz >> h[:bar] ||= :quux => :baz 

并通过比较:

 // continued from above >> h[:bar] = h[:bar] || :quuux Setting bar = baz => :baz 
 CACHE[:some_key] ||= "Some String" 

相当于

 CACHE[:some_key] = "Some String" unless CACHE[:some_key] 

(相当于if + nil?除非CACHE[:some_key]是一个布尔值)。

换句话说:是的, ||=只有在LHS为零或假时才会写入。

[我删除了比其他人更不准确的例子。 我将答案留给可能对某些人感兴趣的基准。 我的观点是:]

所以基本上

 CACHE[:some_key] ||= "Some String" 

是相同的

 CACHE[:some_key] = "Some String" unless CACHE[:some_key] 

我更喜欢第一种语法,但是由于可读性在这种情况下有所降低,所以由你决定。


我很好奇,所以这里有一些基准:

 require "benchmark" CACHE = {} Benchmark.bm do |x| x.report { for i in 0..100000 CACHE[:some_key] ||= "Some String" end } x.report { for i in 0..100000 CACHE[:some_key] = "Some String" unless CACHE[:some_key] end } end user system total real 0.030000 0.000000 0.030000 ( 0.025167) 0.020000 0.000000 0.020000 ( 0.026670)