Является ли рубиновый оператор || = интеллектуальным?
У меня есть вопрос относительно оператора || = в ruby, и это представляет для меня особый интерес, поскольку я использую его для записи в memcache. Мне интересно, делает ли || = сначала проверять приемник, чтобы увидеть, задан ли он перед вызовом этого сеттера, или это буквально псевдоним x = x || y
Это не имеет особого значения в случае нормальной переменной, но использует что-то вроде:
CACHE[:some_key] ||= "Some String"
может сделать запись memcache, которая дороже, чем простой набор переменных. Я не мог найти ничего о || = в ruby api, как ни странно, поэтому сам не смог ответить.
Конечно, я знаю, что:
CACHE[:some_key] = "Some String" if CACHE[:some_key].nil?
достигнет этого, я просто ищу самый краткий синтаксис.
Ответы
Ответ 1
Это очень легко проверить:
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 намереваются выполнить его.
Ответ 2
В соответствии с §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
Итак, согласно спецификации, []=
всегда будет вызван. Но на самом деле это не так в текущих реализациях (я тестировал МРТ, 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
undefined, является ли a
простой переменной или более сложным выражением типа foo[bar]
или foo.bar
и т.д..
См. также некоторые из других экземпляров этого же вопроса, которые уже были заданы и указаны здесь в StackOverflow (например, этот). Кроме того, этот вопрос так много раз обсуждался в списке рассылки ruby-talk, что есть темы обсуждения, единственной целью которых является суммирование другие темы обсуждения. (Хотя учтите, что этот список далеко не завершен.)
Ответ 3
Здесь другая демонстрация, отличающаяся от другой, говорит, что она явно показывает, когда Хэш записывается:
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
Ответ 4
CACHE[:some_key] ||= "Some String"
эквивалентно
CACHE[:some_key] = "Some String" unless CACHE[:some_key]
(что эквивалентно if
+ nil?
, если CACHE[:some_key]
не является булевым значением).
Другими словами: да, ||=
будет записываться только в том случае, если LHS равен нулю или false.
Ответ 5
[Я удалил свой пример, который был менее точным, чем другие. Я оставляю свой ответ за те критерии, которые могут быть интересны некоторым. Моя точка была:]
Итак, в основном
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)