Оператор ruby ​​|| = интеллектуален? - PullRequest
17 голосов
/ 07 июня 2010

У меня есть вопрос относительно оператора || = в ruby, и это представляет особый интерес для меня, так как я использую его для записи в memcache. Что мне интересно, так это то, проверяет ли || = получатель сначала, установлен ли он перед вызовом этого установщика, или это буквально псевдоним x = x || y

Это не имеет большого значения в случае нормальной переменной, но с использованием чего-то вроде:

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

может сделать запись в memcache, которая стоит дороже, чем простой набор переменных. Я не смог найти ничего о || = в ruby ​​api, как ни странно, поэтому сам не смог ответить на этот вопрос.

Конечно, я знаю, что:

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

достиг бы этого, я просто искал самый краткий синтаксис.

Ответы [ 5 ]

15 голосов
/ 07 июня 2010

Это очень легко проверить:

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 показывает , что это по проекту и не должно зависеть от реализации 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проекты намерены его соблюдать.

7 голосов
/ 07 июня 2010

Согласно §11.3.1.2.2 проекта спецификации ISO ,

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

(я использую ω здесь для обозначения любого оператора, так что это может быть ||=, +=, *=, >>=, %=, & hellip;)

Расширяется до:

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 и т. Д.

См. Также некоторые другие примеры того же вопроса, которые уже были заданы и даны ответы здесь, в StackOverflow (например, этот ). Кроме того, этот вопрос обсуждался так много раз в списке рассылки ruby-talk, что теперь есть обсуждений , единственной целью которых является обобщение других обсуждений. (Хотя, пожалуйста, обратите внимание, что этот список далеко не полный.)

1 голос
/ 07 июня 2010

Вот еще одна демонстрация, которая немного отличается от других ответов тем, что она явно показывает, когда записывается хэш:

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
0 голосов
/ 07 июня 2010

[Я удалил свой пример, который был менее точным, чем другой. Я оставляю свой ответ для ориентиров, которые могут быть интересны для некоторых. Моя точка зрения была:]

Так в основном

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)
0 голосов
/ 07 июня 2010
CACHE[:some_key] ||= "Some String"

эквивалентно

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

(что эквивалентно if + nil?, если CACHE[:some_key] не является логическим значением).

Другими словами:да, ||= будет писать только в том случае, если LHS равен нулю или неверен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...