红宝石运算符 ||= 智能吗?

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

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

扩展到:

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 时写入。

【讨论】:

以上是关于红宝石运算符 ||= 智能吗?的主要内容,如果未能解决你的问题,请参考以下文章

红宝石连接和联合运算符[重复]

红宝石,!!运算符(a/k/a 双键)[重复]

红宝石管道操作员

“:foo”在红宝石中是啥意思[重复]

018-智能指针

红宝石块? : 表达式 [重复]