Почему процессы/блоки Ruby с аргументами splat ведут себя не так, как методы и лямбда-выражения?

Почему процессы/блоки Ruby (2.0) с аргументами splat ведут себя не так, как методы и лямбда-выражения?

def foo (ids, *args)
  p ids
end
foo([1,2,3]) # => [1, 2, 3]

bar = lambda do |ids, *args|
  p ids
end
bar.call([1,2,3]) # => [1, 2, 3]

baz = proc do |ids, *args|
  p ids
end
baz.call([1,2,3]) # => 1

def qux (ids, *args)
  yield ids, *args
end
qux([1,2,3]) { |ids, *args| p ids } # => 1

Вот подтверждение такого поведения, но без объяснения причин: http://makandracards.com/makandra/20641-careful-when-calling-a-ruby-block-with-an-array


person Jordan    schedule 30.05.2014    source источник
comment
Если вы хотите улучшить свой вопрос, join... только излишне усложняет его. Это не имеет отношения к вашему вопросу. Все, что вам нужно сделать, это сделать p ids в каждом блоке и прояснить, чем он отличается.   -  person sawa    schedule 30.05.2014
comment
Вероятно, это как-то связано с тем, что proc является стандартным библиотечным методом, а lambda является специальным ключевым словом...   -  person Idan Arye    schedule 30.05.2014
comment
@sawa Спасибо за предложение!   -  person Jordan    schedule 30.05.2014
comment
Думали, что вам пришлось обновить Proc?   -  person Tony Hopkinson    schedule 30.05.2014
comment
@IdanArye, я добавил код, иллюстрирующий, что уступка блоку ведет себя так же. В Ruby 2.0 proc и Proc.new — это одно и то же: ruby-doc.org/core-2.0/Kernel.html#method-i-proc   -  person Jordan    schedule 30.05.2014
comment
Ваша добавленная ссылка является связанным вопросом, но это не одно и то же. В этом вопросе знак является необходимостью; он используется для сохранения arity-mismatch . Ваш вопрос выдает более интересный случай. *args делает арность необязательной, но знак все равно применяется. Я подозреваю, что это может быть ошибка.   -  person sawa    schedule 30.05.2014
comment
ruby-doc.org/core- 2.1.1/Proc.html#method-i-lambda-3F (это называется трюки) — это не ответ на вопрос «почему?», а хорошее объяснение.   -  person Victor Moroz    schedule 30.05.2014
comment
Такое поведение отсталое. Lambdas, которые фактически применяют количество аргументов, с удовольствием назначают список только его первому параметру. Proc, которому все равно, сколько аргументов вы передаете, на самом деле распределяет массив по своим аргументам. Интуитивно я ожидал бы, что это поведение будет полностью изменено.   -  person Martin Konecny    schedule 30.05.2014
comment
@sawa на самом деле это одно и то же, а именно уловки, упомянутые в комментарии Виктора. Это происходит, когда определение также не включает знак. Проблема в том, что процессы, не являющиеся лямбда-выражениями, автоматически добавляют свой первый аргумент, если это массив. Я взломал его в своем коде, обернув свой первый массив аргументов в другой массив, если только лямбда? верно, так как внешний массив будет развернут.   -  person Jordan    schedule 30.05.2014
comment
В то же время вы можете выполнять деструктурирование и в лямбда-выражениях: f = ->((x, *xs)) { ... }, так что не-лямбда-блок, вероятно, может рассматриваться как лямбда-выражение с неявными скобками.   -  person Victor Moroz    schedule 30.05.2014
comment
@IdanArye lambda и proc оба метода: [3] pry(main)> method :lambda => #<Method: Object(Kernel)#lambda> Я проверил это на Ruby 1.9.3 и 2.0.0.   -  person Darek Nędza    schedule 30.05.2014
comment
@DarekNędza Хорошо, это странно, но в Ruby 2.1.1 lambda также является ключевым словом. Когда вы используете его напрямую, возврат из тела лямбды не возвращается из включающего метода, но когда вы используете его через method(:lambda).call, он ведет себя как обычный метод с блоком, а возврат из блока возвращает из вмещающего метода.   -  person Idan Arye    schedule 30.05.2014
comment
@IdanArye Я не могу проверить это на 2.1.*, но в 2.0 и 1.9.3 lambda/-> возвращается из блока, НО proc/Proc.new поднимает LocalJumpError: unexpected return. Это действительно странно, потому что в упомянутых версиях lambda работает так, как я описал. Это может быть ошибка 2.1.*.   -  person Darek Nędza    schedule 30.05.2014
comment
@VictorMoroz Пожалуйста, не стесняйтесь добавлять ответ, обобщающий ваши знания, и я приму его. Вы определенно идентифицировали поведение, даже если мы не знаем, почему кто-то захочет всегда ставить один массив, переданный в блок...   -  person Jordan    schedule 31.05.2014


Ответы (2)


Существует два типа объектов Proc: lambda, которые обрабатывают список аргументов так же, как обычный метод, и proc, которые используют «трюки» (Proc#lambda?). proc будет выделять массив, если это единственный аргумент, игнорировать лишние аргументы, назначать nil отсутствующим. Вы можете частично имитировать поведение proc с помощью lambda, используя деструктурирование:

->((x, y)) { [x, y] }[1]         #=> [1, nil]
->((x, y)) { [x, y] }[[1, 2]]    #=> [1, 2]
->((x, y)) { [x, y] }[[1, 2, 3]] #=> [1, 2]
->((x, y)) { [x, y] }[1, 2]      #=> ArgumentError
person Victor Moroz    schedule 31.05.2014

Только что столкнулся с похожей проблемой!

В любом случае, мои основные выводы:

  1. Оператор splat работает для назначения массива предсказуемым образом.

  2. Процедуры эффективно назначают аргументы для ввода (см. Заявление об отказе от ответственности ниже)

Это приводит к странному поведению, т.е. пример выше:

baz = proc do |ids, *args|
  p ids
end
baz.call([1,2,3]) # => 1

Так что же происходит? [1,2,3] передается в baz, который затем присваивает массив своим аргументам

ids, *args = [1,2,3]
ids = 1
args = [2,3]

При запуске блок проверяет только ids, то есть 1. На самом деле, если вы вставите p args в блок, вы обнаружите, что это действительно [2,3]. Определенно не тот результат, который можно было бы ожидать от метода (или лямбды).

Отказ от ответственности: я не могу с уверенностью сказать, просто ли процедуры назначают свои аргументы для ввода под капотом. Но похоже, что это соответствует их поведению, когда не применяется правильное количество аргументов. На самом деле, если вы даете процедуре слишком много аргументов, она игнорирует лишние. Слишком мало, и это проходит в нулях. Точно так же, как присваивание переменных.

person phil-ociraptor    schedule 12.06.2014
comment
Ха-ха, это именно то, что я делал, вызвало эту проблему. См. здесь и здесь для обходного пути, который я использовал, который в основном заключался в переносе ids в массиве unless lambda?. В коде приложения этот хак, вероятно, не понадобится, но здесь он используется в коде библиотеки, потому что я хотел разрешить лямбду, обернутый процесс или развернутый блок в качестве аргумента метода. - person Jordan; 13.06.2014