ruby运算符|| =智能吗?

bra*_*rad 17 ruby memcached

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

这在普通变量的情况下并不重要,但使用类似的东西:

CACHE[:some_key] ||= "Some String"
Run Code Online (Sandbox Code Playgroud)

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

我当然知道:

CACHE[:some_key] = "Some String" if CACHE[:some_key].nil?
Run Code Online (Sandbox Code Playgroud)

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

mol*_*olf 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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

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


Jör*_*tag 7

根据ISO规范草案的 §11.3.1.2.2 ,

CACHE[:some_key] ||= "Some String"
Run Code Online (Sandbox Code Playgroud)

扩展到

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

或者,在更一般的情况下

primary_expression[indexing_argument_list] ?= expression
Run Code Online (Sandbox Code Playgroud)

(我用?在这里表示任何操作,因此它可能是||=,+=,*=,>>=, %=,...)

扩展到:

o = primary_expression
*l = indexing_argument_list
v = o.[](*l)
w = expression
x = v ? w
l << x
o.[]=(*l)
x
Run Code Online (Sandbox Code Playgroud)

因此,根据规范,[]=始终被调用.但实际情况并非如此(我测试了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
Run Code Online (Sandbox Code Playgroud)

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

通常,作为第一近似值

a ||= b
Run Code Online (Sandbox Code Playgroud)

扩展到

a || a = b
Run Code Online (Sandbox Code Playgroud)

然而,有各种涉及,例如subleties的,无论是否a是不确定的,是否a是一个简单的变量或更复杂的表达像foo[bar]foo.bar等等.

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