Другое поведение `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.