Различное поведение `public_send` в Ruby 2.6 / 2.7 - PullRequest
1 голос
/ 25 февраля 2020
class A
  def a
    1
  end
end

a = A.new
x = {}

a.a(**x) # => 1 in both Ruby 2.6 and 2.7

a.public_send(:a, **x) # => 1 in Ruby 2.7

В Ruby 2.6, однако:

ArgumentError: wrong number of arguments (given 1, expected 0) 

Это ошибка в версии до 2.7 public_send / send / __send__? Что бы вы предложили, чтобы преодолеть эту разницу?

Вы можете проверить этот сбой в прямом эфире здесь .

1 Ответ

4 голосов
/ 25 февраля 2020

В Ruby 2.6 и ранее синтаксис **argument был в основном (но не полностью) синтаксическим c сахаром для пройденного га sh. Это было сделано для того, чтобы конвенция передавала переменную ha sh в качестве последнего аргумента методу.

При Ruby 2.7 аргументы ключевых слов, однако, семантически обновлялись и не отображались в ha sh параметр больше. Здесь аргументы ключевого слова обрабатываются из позиционных аргументов.

В Ruby 2.6 и ранее следующие два определения метода, где (по многим аспектам по крайней мере) эквивалентны:

def one(args={})
  #...
end

def two(**args)
  #...
end

В обоих случаях вы можете передать либо дословно ha sh, либо разделенную ha sh с одинаковым результатом:

arguments = {foo: :bar}

one(arguments)
one(**arguments)

two(arguments)
two(**arguments)

С Ruby 2.7 однако вы должны передать аргументы ключевого слова как таковые (предыдущее поведение все еще работает, но не рекомендуется с предупреждением). Таким образом, вызов two(arguments) приведет к предупреждению об устаревании в 2.7 и станет недействительным в Ruby 3.0.

Внутренне, аргумент ha sh с разделителями (который передает аргументы ключевого слова в метод) таким образом, получается пустой список аргументов ключевого слова в Ruby 2.7, но позиционный аргумент с пустым Ha sh в 2.6.

Вы можете увидеть, что здесь происходит подробно, проверив, как Ruby интерпретирует аргументы к его public_send методу. В Ruby 2.6 и более ранних версиях метод имеет следующий интерфейс:

def public_send26(method_name, *args, &block);
  p method_name
  p args

  # we then effectively call
  #    self.method_name(*args, &block)
  # internally from C code

  nil
end

При вызове этого метода в Ruby 2.6 как public_send26(:a, **{}) вы увидите, что аргументы ключевого слова снова " "обернуто" в га sh:

:a
[{}]

С Ruby 2.7 вместо этого у вас есть следующий эффективный интерфейс:

def public_send27(method_name, *args, **kwargs, &block);
  p method_name
  p args
  p **kwargs

  # Here, we then effectively call
  #    self.method_name(*args, **kwargs, &block)
  # internally from C code

  nil
end

Вы можете видеть, что аргументы ключевого слова являются отдельными обрабатывается и сохраняется как аргументы ключевых слов в Ruby 2.7, а не обрабатывается как обычный позиционный аргумент Ha sh для метода, как это было сделано в Ruby 2.6 и более ранних версиях.

Ruby 2.7 все еще содержит резервное поведение, так что код, ожидающий поведение Ruby 2.6, все еще работает (хотя и с предупреждением). В Ruby 3.0 вам НЕОБХОДИМО строго разделять аргументы ключевых слов и позиционные аргументы. Вы можете найти дополнительное описание этих изменений в новостной записи на ruby -lang.org .

...