Другое поведение `public_send` в Ruby 2.6 / 2.7

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 ответ

Решение

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

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

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

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

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

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

arguments = {foo: :bar}

one(arguments)
one(**arguments)

two(arguments)
two(**arguments)

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

Внутри хэш-аргумент со знаками (который передает аргументы ключевого слова в метод), таким образом, приводит к пустому списку аргументов ключевого слова в Ruby 2.7, но позиционному аргументу с пустым хешем в 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, **{}), вы увидите, что аргументы ключевого слова снова "завернуты" в хэш:

: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, а не как обычный позиционный аргумент Hash для метода, как это было сделано в Ruby 2.6 и ранее.

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

Другие вопросы по тегам