Странное поведение метода с необязательным первым хеш-параметром и ключевым словом_args

У меня есть следующий метод:

def test(first_param = nil, keyword_arg: nil)
  puts "first_param: #{first_param}"
  puts "keyword_arg: #{keyword_arg}"
end

Все следующие вызовы делают то, что я от них ожидаю:

test(:something)
#=> first_param: something
#   keyword_arg:

test(nil, keyword_arg: :keyword_arg)
#=> first_param:
#   keyword_arg: keyword_arg

test({ first_param: :is_a_hash }, keyword_arg: :is_still_working)
#=> first_param: {:first_param=>:is_a_hash}
#   keyword_arg: is_still_working

Но опускание необязательного keyword_arg и передача хэша в качестве первого аргумента дает мне ошибку:

test(first_param: :is_a_hash)
#=> test.rb:1:in `test': unknown keyword: first_param (ArgumentError)
#           from test.rb:12:in `<main>'

Я ожидаю, что он установит first_param на { first_param: :is_hash }, а keyword_arg на nil.

Кажется, он интерпретирует каждый хэш как ключевое слово arg:

test(keyword_arg: :should_be_first_param)
#=> first_param:
#   keyword_arg: should_be_first_param

Это должно было установить first_param в { keyword_arg: :should_be_first_param }, оставив, по моему мнению, keyword_arg nil.

Это ошибка парсера или ожидаемое поведение? Проверено на рубине 2.3.0 и 2.2.4.


Изменить: сделать первый параметр обязательным, и все работает так, как я ожидал:

def test_mandatory(first_param, keyword_arg: nil)
  puts "first_param: #{first_param}"
  puts "keyword_arg: #{keyword_arg}"
end

test_mandatory(first_param: :is_a_hash)
#=> first_param: {:first_param=>:is_a_hash}
#   keyword_arg:

test_mandatory(keyword_arg: :should_be_first_param)
#=> first_param: {:keyword_arg=>:should_be_first_param}
#   keyword_arg:

Я ожидаю, что необязательный параметр не изменит способ анализа параметров.

Я открыл проблему на bugs.ruby-lang.org, чтобы разработчики могли разобраться независимо от того, предназначено ли это для этого или является побочным эффектом kword args.


person Markus    schedule 07.01.2016    source источник
comment
Это должно было установить first_param на { keyword_arg: :should_be_first_param }, оставив, по моему мнению, keyword_arg nil. -- Почему? Разве вы не ожидали, что это будет двусмысленно?   -  person sawa    schedule 07.01.2016
comment
Я даю только один параметр методу. Если это не хеш, он устанавливает first_param (test(:something)). Но если параметр является хэшем, он не устанавливает first_param, а интерпретирует хэш как аргументы ключевого слова. Почему поведение зависит от типа параметра? Это не логично (по крайней мере для меня).   -  person Markus    schedule 07.01.2016


Ответы (1)


Ожидается согласно ответу Марка-Андре Лафортуна:

Такое поведение может показаться неожиданным, но оно преднамеренное.

Это сводится к тому, чтобы сначала отдать приоритет заполнению аргументов ключевого слова, а не заполнению безымянных параметров. На самом деле это единственный возможный путь. Среди прочего, подумайте о следующем примере:

def foo(*rest, bar: 42)
end

Если мы сначала не установим приоритет именованных аргументов, то в этом примере просто невозможно указать значение для bar!

Итак, Ruby проверяет, что:

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

=> тогда этот параметр используется для аргументов ключевого слова.

Обратите внимание на требование, чтобы ключи были символами. Это может привести к еще большему удивлению, если вы передадите хэш с некоторыми ключами, которые не являются символами:

def foo(a = nil, b: nil)
  p a, b
end
foo(:b  => 42) # => nil, 42
foo('b' => 42) # => {"b" => 42}, nil
person Stefan    schedule 14.01.2016