Рубиновый оператор || = интеллектуален?

У меня есть вопрос относительно || = оператор в рубине, и это особенно интересно для меня, поскольку я использую его для записи в кэш-память. То, что я задаюсь вопросом, делает || =, проверяют получатель сначала, чтобы видеть, установлен ли он прежде, чем назвать тот метод set или является этим буквально псевдоним к x = x || y

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

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

мог возможно сделать запись кэш-памяти, которая является более дорогой, чем набор простой переменной. Я ничего не мог найти о || = в рубиновом API достаточно странно, таким образом, я не смог ответить на это сам.

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

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

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

17
задан brad 7 June 2010 в 15:22
поделиться

5 ответов

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

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 намерены его соблюдать.

15
ответ дан 30 November 2019 в 13:27
поделиться

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

Итак, в основном

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
ответ дан 30 November 2019 в 13:27
поделиться

Согласно §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

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

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

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

.
7
ответ дан 30 November 2019 в 13:27
поделиться

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

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
1
ответ дан 30 November 2019 в 13:27
поделиться
CACHE[:some_key] ||= "Some String"

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

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

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

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

0
ответ дан 30 November 2019 в 13:27
поделиться
Другие вопросы по тегам:

Похожие вопросы: