Распаковка аргументов ключевых слов (splat) в Ruby

то, что происходит ниже, кажется мне немного странным.

def f(a, b)
  puts "#{a} :: #{b}"
end

f(*[1, 2], **{}) # prints "1 :: 2"

hash = {}
f(*[1, 2], **hash)
ArgumentError: wrong number of arguments (3 for 2)

f(*[1, 2], **Hash.new)
ArgumentError: wrong number of arguments (3 for 2)

это функция оптимизации компилятора?

3 ответов


Это ошибка Ruby, о которой сообщалось несколько раз (например,здесь мной), но не было зафиксировано.

Я предполагаю, что с момента введения функции аргумента ключевого слова синтаксис double splat стал мутным, и это является косвенной причиной этой ошибки. Я слышал, что Matz рассматривает возможность введения нового синтаксиса в некоторой будущей версии Ruby для различения хэшей и аргументов ключевых слов.


[Edit:я видел ответ @sawa после завершения моего. Я был прав: это жук!]

что разные результаты получаются, когда литеральный пустой хэш дважды splatted и пустой хэш, который является значением переменной double-splatted, кажется мне prima facia доказательств, что это из-за ошибки в Ruby. Чтобы понять, почему ошибка может существовать, рассмотрим сначала причину передачи метода с двойным splatted хэшем.

Предположим, мы определяем способ с некоторыми аргументами ключевого слова:

def my_method(x, a: 'cat', b: 'dog')
  [x, a, b]
end

my_method(1)
  #=> [1, "cat", "dog"] 

значения по умолчанию применяются к двум аргументам ключевого слова. Теперь попробуйте:

my_method(1, a: 2)
  #=> [1, 2, "dog"]

теперь давайте использовать двойной хэш.

h = { a: 2, b: 3 }

my_method(1, **h)
 #=> [1, 2, 3] 

это работает так же с необходимыми аргументами ключевых слов (Ruby 2.1+).

def my_method(x, a:, b:)
  [x, a, b]
end

my_method(1, **h)
  #=> [1, 2, 3]

однако для использования хэша с двойным splatted в качестве аргумента хэш не может содержать ключи, которые не перечислены в качестве аргументов в определении метода.

def my_method(x, a:)
  [x, a]
end

h = { a: 2, b: 3 }

my_method(1, **h)
  #=> ArgumentError: unknown keyword: b

поэтому возникает вопрос: можно ли передать в качестве аргумента пустой хэш с двойным splatted, учитывая, что все ключи хэша (none) включены в качестве аргументов в определение метода (в этом случае это не будет иметь никакого эффекта)? Давай попробуем.

def my_method(x)
  [x]
end

my_method(1, **{})
  #=> [1]

да!

h = {}
my_method(1, **h)
  #=> ArgumentError: wrong number of arguments (given 2, expected 1)

нет!

это не имеет смысла. Итак, предполагая, что это ошибка, как она могла возникнуть? Я подозреваю, что это может быть связано с оптимизацией Ruby, как было предложено OP. Это пустой хэш-литерал, с ним можно справиться раньше в коде Ruby, чем если бы это было значение переменной. Я предполагаю, что тот, кто написал предыдущий код, ответил "Да" на вопрос, который я поставил выше, и тот, кто написал последний код, ответил "Нет" или не рассмотрел случай пустого хэша в этот момент.

если эта теория ошибок не сбита, OP или кто-то другой должен сообщить об этом.


У меня такое чувство, что вы спотыкаетесь о особенность оператора splat по отношению к этой пустой хэш-структуре. Кажется, что разбрызгивание пустого встроенного хэша приводит к его исчезновению, но все остальное расширяется как аргумент.

на самом деле это может быть ошибка в Ruby, хотя это такой причудливый случай edge, я не очень удивлен.

код f функция не принимает аргументы ключевого слова любого вида, поэтому, если достаточно энергичная попытка чтобы их это не выходит. Последние два примера, похоже, пытаются принудительно ввести пустой хэш в качестве буквального аргумента.