У меня есть вопрос относительно || = оператор в рубине, и это особенно интересно для меня, поскольку я использую его для записи в кэш-память. То, что я задаюсь вопросом, делает || =, проверяют получатель сначала, чтобы видеть, установлен ли он прежде, чем назвать тот метод set или является этим буквально псевдоним к x = x || y
Это действительно не имело бы значения в случае нормальной переменной, но использования чего-то как:
CACHE[:some_key] ||= "Some String"
мог возможно сделать запись кэш-памяти, которая является более дорогой, чем набор простой переменной. Я ничего не мог найти о || = в рубиновом API достаточно странно, таким образом, я не смог ответить на это сам.
Конечно, я знаю что:
CACHE[:some_key] = "Some String" if CACHE[:some_key].nil?
достиг бы этого, я просто ищу самый краткий синтаксис.
Это очень легко проверить:
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 намерены его соблюдать.
[Я удалил свой пример, который был менее точным, чем другие. Я оставляю свой ответ для тестов, которые могут быть интересны некоторым. Моя точка зрения была такой:]
Итак, в основном
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)
Согласно §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, что теперь существуют темы обсуждения, единственной целью которых является подведение итогов других тем обсуждения. (Хотя, пожалуйста, обратите внимание, что этот список далеко не полный)
.Вот еще одна демонстрация, которая немного отличается от других ответов тем, что явно показывает, когда хэш записывается в:
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
CACHE[:some_key] ||= "Some String"
эквивалентен
CACHE[:some_key] = "Some String" unless CACHE[:some_key]
(который эквивалентен if
+ nil?
, если CACHE[:some_key]
не является булевым значением).
Другими словами: да, ||=
будет записано только если LHS равно nil или false.