Подтвердить что ты не робот

Является ли рубиновый оператор || = интеллектуальным?

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

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

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

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

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

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

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

4b9b3361

Ответ 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)