Смешивание аргумента ключевого слова и аргументов со значениями по умолчанию дублирует хэш?

Итак, я обнаружил это рубиновое поведение, которое сводило меня с ума больше часа. Когда я передаю хэш функции, которая имеет значение по умолчанию для хеша И аргумент ключевого слова, кажется, что ссылка передается неправильно. Как только я убираю значение по умолчанию ИЛИ аргумент ключевого слова, функция ведет себя так, как ожидалось. Я пропустил здесь какое-то очевидное рубиновое правило?

def change_hash(h={}, rand: om)
  h['hey'] = true
end

k = {}
change_hash(k)
k
#=> {}

Он отлично работает, как только я убираю значение по умолчанию или ключевое слово arg.

def change_hash(h, rand: om)
  h['hey'] = true
end

k = {}
change_hash(k)
k
#=> {'hey' => true}

def change_hash(h={})
  h['hey'] = true
end

k = {}
change_hash(k)
k
#=> {'hey' => true}

РЕДАКТИРОВАТЬ

Спасибо за ваши ответы. Большинство из вас отметили, что ruby ​​в некоторых случаях анализирует хэш как аргумент ключевого слова. Однако я говорю о случае, когда хэш имеет строковые ключи. Когда я передаю хеш, кажется, что переданное значение верно. Но изменение хеша внутри функции не изменяет исходный хеш.

def change_hash(hash={}, another_arg: 300)
  puts "another_arg: #{another_arg}"
  puts "hash: #{hash}"
  hash['hey'] = 3
end

my_hash = {"o" => 3}
change_hash(my_hash)
puts my_hash 

Распечатывает

another_arg: 300
hash: {"o"=>3}
{"o"=>3}

person ivanibash    schedule 02.02.2018    source источник


Ответы (2)


TL;DR ruby ​​позволяет передавать хэш в качестве аргумента ключевого слова, а также «расширенный хэш на месте». Поскольку change_hash(rand: :om) должен быть перенаправлен на аргумент ключевого слова, то же самое следует сделать и с change_hash({rand: :om}) и, следовательно, с change_hash({}).


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

С другой стороны, поскольку в Ruby отсутствует функция сопоставления с образцом для предложений функций, синтаксический анализ данного аргумента для принятия решения о том, следует ли передавать его как двойное знаковое пятно или нет, приведет к огромным потерям производительности. Поскольку вызов с явным аргументом ключевого слова (change_hash(rand: :om)) обязательно должен передать :om в аргумент ключевого слова, и мы можем передать явный хэш {rand: :om} в качестве аргумента ключевого слова, Ruby ничего не остается делать, кроме как принять < em>любой хэш в качестве аргумента ключевого слова.


Ruby разделит единственный хэш-аргумент между hash и rand:

k = {"a" => 42, rand: 42}
def change_hash(h={}, rand: :om)
  h[:foo] = 42
  puts h.inspect
end
change_hash(k);
puts k.inspect

#⇒ {"a"=>42, :foo=>42}
#⇒ {"a"=>42, :rand=>42}

Эта функция разделения требует, чтобы аргумент был клонирован перед передачей. Вот почему исходный хэш не изменяется.

person Aleksei Matiushkin    schedule 02.02.2018
comment
Но почему тогда кажется, что я получаю правильные значения внутри функции? repl.it/repls/SafeCanineAndeancat - person ivanibash; 02.02.2018
comment
Я не уверен, что следую. Что вы подразумеваете под «получением правильных значений»? В repl вы передаете два аргумента, по умолчанию нет. - person Aleksei Matiushkin; 02.02.2018
comment
ой извините, неправильный ответ. обновлен repl.it/repls/SafeCanineAndeancat - person ivanibash; 02.02.2018
comment
Я все еще сомневаюсь, что следую. hash печатает значение по умолчанию, а не то, которое вы передали. Проверьте, что это __id__. - person Aleksei Matiushkin; 02.02.2018
comment
откуда я сижу, распечатанный хэш - это тот, который я передаю, а не тот, который используется по умолчанию: / Идентификатор другой, да, я ожидаю, где проблема. Похоже, что хеш клонируется за кулисами. - person ivanibash; 02.02.2018
comment
@redFur в этом случае вы передали хэш со строковым ключом "o". Хеши со строковыми ключами не интерпретируются как аргументы ключевых слов, поэтому Ruby был достаточно умен, чтобы передать их в первый необязательный аргумент. Если вы измените его на my_hash = {o: 3}, вы получите ArgumentError (как в моем ответе ниже). - person Aliaksei Kliuchnikau; 02.02.2018
comment
А, вот и все, спасибо @AliakseiKliuchnikau. Почему же тогда переданный хеш не меняется после выполнения функции? - person ivanibash; 02.02.2018
comment
@redFur, ну это немного неожиданное поведение - вы нашли еще один интересный случай - я постараюсь найти объяснение - person Aliaksei Kliuchnikau; 02.02.2018
comment
@AliakseiKliuchnikau спасибо, я думал, что схожу с ума :) Все спешат указать на то, что синтаксический анализ хэша как аргумента ключевого слова. Это правильно, но здесь вопрос идет дальше. - person ivanibash; 02.02.2018
comment
о ничего себе, ха-ха, хорошая работа, выяснить это, довольно сумасшедший. Спасибо, теперь я могу спать. - person ivanibash; 02.02.2018

Это особенно сложный случай в Ruby.

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

change_hash({rand1: 'om'})
# ArgumentError: unknown keyword: rand1

Чтобы обойти это, вы можете передать два отдельных хэша в метод, при этом второй (тот, что для аргументов ключевого слова) будет пустым:

def change_hash(h={}, rand: 'om')
  h['hey'] = true
end

k = {}
change_hash(k, {})
k
#=> {'hey' => true}

С практической точки зрения лучше избегать такой подписи метода в рабочем коде, потому что при использовании метода очень легко сделать ошибку.

person Aliaksei Kliuchnikau    schedule 02.02.2018