Несоответствие арности между 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.

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

Есть два вида Procs: лямбды и не лямбды (как ни странно, последние обычно также называют Procс). Лямбды ведут себя как методы с точки зрения того, как return Ключевое слово ведет себя и (что более важно для этого случая), как они связывают аргументы, тогда как не лямбда ProcS ведут себя как блоки с точки зрения того, как return и аргумент обязательной работы. Вот почему Proc.new (который создает не лямбда Proc) работает, но lambda (что, очевидно, создает лямбда) не делает.

Вы можете проверить, является ли Proc это лямбда или нет, позвонив Proc#lambda?,

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

lambda do |(key, value)|

И, да, более разумный подход к привязке аргументов для блоков, Procs и лямбды были одним из основных несовместимых изменений в Ruby 1.9.

Другие вопросы по тегам