ruby运算符|| =智能吗?

时间:2010-06-07 13:22:30

标签: ruby memcached

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

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

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

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

当然我知道:

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

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

5 个答案:

答案 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)