Несоответствие арности между Hash.each и лямбдами
Я поднял следующий пример от Джоша Сусера
def strip_accents params
thunk = lambda do |key,value|
case value
when String then value.remove_accents!
when Hash then value.each(&thunk)
end
end
params.each(&thunk)
end
Когда я помещаю его в консоль rails (irb) и вызываю его с помощью хэша, я получаю следующее:
ruby-1.9.2-p136 :044 > `ruby --version`
=> "ruby 1.9.2p136 (2010-12-25 revision 30365) [i686-linux]\n"
ruby-1.9.2-p136 :045 > strip_accents({:packs=>{:qty=>1}})
ArgumentError: wrong number of arguments (1 for 2)
from (irb):32:in `block in strip_accents'
from (irb):37:in `each'
from (irb):37:in `strip_accents'
from (irb):45
from /longpathtrimedforclarity/console.rb:44:in `start'
from /longpathtrimedforclarity/console.rb:8:in `start'
from /longpathtrimedforclarity/commands.rb:23:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
Я понимаю, что лямбды проверяют арность, но я вижу два аргумента в определении лямбды. Если я изменю lambda do
в Proc.new do
, Код выполняется, и я получаю ожидаемый результат.
Пример Джоша из 2008 года, поэтому я предполагаю, что это разница в Ruby 1.8 и 1.9. Что тут происходит?
2 ответа
Действительно, кажется, что он изменился между 1.8 и 1.9, но это изменение исправляет его для 1.9.2, по крайней мере, в моих тестах:
def strip_accents params
thunk = lambda do |h|
key, value = h
case value
when String then value.remove_accents!
when Hash then value.each(&thunk)
end
end
params.each(&thunk)
end
Этот подход оказывается обратно совместимым и с Ruby 1.8.7.
Hash#each
как и все остальные #each
метод, возвращает один аргумент в блок. В случае Hash#each
, этот аргумент является двухэлементным массивом, состоящим из ключа и значения.
Так, Hash#each
дает один аргумент, но ваша лямбда имеет два обязательных параметра, поэтому вы получаете ошибку arity.
Он работает с блоками, поскольку блоки менее строги в отношении своих аргументов, и, в частности, если блок имеет несколько параметров, но получает только один аргумент, он будет пытаться деконструировать аргумент, как если бы он был передан с помощью знака сплат.
Есть два вида Proc
s: лямбды и не лямбды (как ни странно, последние обычно также называют Proc
с). Лямбды ведут себя как методы с точки зрения того, как return
Ключевое слово ведет себя и (что более важно для этого случая), как они связывают аргументы, тогда как не лямбда Proc
S ведут себя как блоки с точки зрения того, как return
и аргумент обязательной работы. Вот почему Proc.new
(который создает не лямбда Proc
) работает, но lambda
(что, очевидно, создает лямбда) не делает.
Вы можете проверить, является ли Proc
это лямбда или нет, позвонив Proc#lambda?
,
Если вы хотите деконструировать аргумент, вам придется делать это явно, так же, как при определении метода:
lambda do |(key, value)|
И, да, более разумный подход к привязке аргументов для блоков, Proc
s и лямбды были одним из основных несовместимых изменений в Ruby 1.9.