我对ruby中的|| =语句有疑问,这对我来说特别有意义,因为我正在使用它来写入memcache。我想知道的是,|| =先检查接收器,看它是否在调用该setter之前设置,或者它实际上是x = x || y
的别名
在普通变量的情况下,这并不重要,但使用的方法如下:
CACHE[:some_key] ||= "Some String"
可能会执行memcache写入,这比简单的变量集更昂贵。我在ruby api中找不到任何关于|| =的奇怪信息,所以我自己无法回答这个问题。
当然我知道:
CACHE[:some_key] = "Some String" if CACHE[:some_key].nil?
会实现这一点,我只是在寻找最简洁的语法。
答案 0 :(得分:15)
这非常容易测试:
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 shows的以下摘录,按设计,并且不应依赖于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实施项目都打算遵守它。
答案 1 :(得分:7)
根据Draft ISO Specification的§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
(我在这里使用ω
来表示任何运算符,因此它可以是||=
,+=
,*=
,{{1 },>>=
,...)
扩展为:
%=
因此,根据规范,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
或{{ 1}}等等。
另请参阅此问题的其他一些实例,这些实例已经在StackOverflow上得到了解答(例如,this one)。此外,在ruby-talk邮件列表上已经多次讨论过这个问题,现在discussion threads的唯一目的是总结其他讨论主题。 (虽然请注意,该列表还远未完成。)
答案 2 :(得分:1)
这是另一个与其他答案略有不同的演示,因为它明确显示何时写入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
答案 3 :(得分:0)
CACHE[:some_key] ||= "Some String"
相当于
CACHE[:some_key] = "Some String" unless CACHE[:some_key]
(相当于if
+ nil?
,除非CACHE[:some_key]
是布尔值。)
换句话说:是的,||=
只会在LHS为零或错误时写入。
答案 4 :(得分:0)
[我删除了一个不如其他人准确的例子。我将答案留给可能对某些人感兴趣的基准。我的观点是:]
所以基本上
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)