红宝石运算符 ||= 智能吗?
Posted
技术标签:
【中文标题】红宝石运算符 ||= 智能吗?【英文标题】:Is the ruby operator ||= intelligent? 【发布时间】:2011-02-28 17:05:04 【问题描述】:我有一个关于 ruby 中的 ||= 语句的问题,这对我来说特别感兴趣,因为我正在使用它来写入内存缓存。我想知道的是,是否 ||= 在调用该设置器之前先检查接收器是否已设置,或者它实际上是 x = x || y
的别名
这在普通变量的情况下并不重要,但使用类似的东西:
CACHE[:some_key] ||= "Some String"
可能会进行比简单变量集更昂贵的内存缓存写入。奇怪的是,我在 ruby api 中找不到任何关于 ||= 的信息,所以我自己无法回答这个问题。
我当然知道:
CACHE[:some_key] = "Some String" if CACHE[:some_key].nil?
会实现这一点,我只是在寻找最简洁的语法。
【问题讨论】:
标题似乎对 ruby 不公平(作为生成的 slug) @dzen 不确定您的意思? 阅读网址,卢克“is-ruby-intelligent” 【参考方案1】:这非常容易测试:
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
在同一个文件中,[]
和 []=
有一个类似的规范要求相同的行为。
虽然 Ruby 规范仍在进行中,但很明显,主要的 Ruby 实施项目都打算遵守它。
【讨论】:
有趣的是,当前的 ISO Ruby 规范草案正好相反:分配必须总是发生。但是,我认为这是一个错误,因为 ISO 规范的目的是描述所有 Ruby 实现的当前行为(的一个子集)。它还首先否定了||=
的全部目的。【参考方案2】:
根据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
(我在这里使用ω
来表示任何 运算符,所以它可以是||=
、+=
、*=
、>>=
、%=
、...)
扩展到:
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
等等。
另请参阅 *** 上已在此处提出和回答的同一问题的其他一些实例(例如,this one)。此外,这个问题已经在 ruby-talk 邮件列表中讨论过很多次,现在有 discussion threads 的唯一目的是总结其他讨论线程。 (但请注意,该列表远未完成。)
【讨论】:
感谢您的 || a = b 解释。这清楚地表明我们只设置 if !a。 @brad:正确,尽管规范实际上是相反的。我目前正在调查这个问题。 @Jorg:你得出什么结论了吗?【参考方案3】:[我删除了我的示例,该示例不如其他示例准确。我将我的答案留给某些人可能感兴趣的基准。我的意思是:]
基本上是这样
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)
【讨论】:
即使你是对的,你的例子也没有提供任何证据。如果CACHE[:some_key] ||= "Some String2"
会扩展为CACHE[:some_key] = CACHE[:some_key] || "Some String2"
,它也会评估为"Some string"
。【参考方案4】:
这是另一个与其他答案有点不同的演示,它明确显示了何时写入哈希:
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
【讨论】:
【参考方案5】:CACHE[:some_key] ||= "Some String"
等价于
CACHE[:some_key] = "Some String" unless CACHE[:some_key]
(相当于if
+ nil?
,除非CACHE[:some_key]
是布尔值)。
换句话说:是的,||=
只会在 LHS 为 nil 或 false 时写入。
【讨论】:
以上是关于红宝石运算符 ||= 智能吗?的主要内容,如果未能解决你的问题,请参考以下文章