Inconsistency of arity between Hash.each and lambdas

六眼飞鱼酱① 提交于 2019-12-12 07:20:45

问题


I lifted the following example from Josh Susser

  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

when I put it in the the rails console (irb), and call it with a hash, I get the following:

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>'

I understand that lambdas check arity, but I see two arguments in the lambda definition. If I change lambda do to Proc.new do, The code executes, and I get the expected result.

Josh's example is from 2008, so I'm assuming this is a difference in Ruby 1.8 and 1.9. What's going on here?


回答1:


Indeed, it appears to have changed between 1.8 and 1.9, but this change fixes it for 1.9.2, at least in my tests:

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

This approach turns out to be backward-compatible with Ruby 1.8.7, as well.




回答2:


Hash#each, just like every other #each method, yields one argument to the block. In the case of Hash#each, that one argument is a two-element array consisting of the key and the value.

So, Hash#each yields one argument, but your lambda has two mandatory parameters, therefore you get an arity error.

It works with blocks, since blocks are less strict about their arguments, and in particular, if a block has multiple parameters, but only gets one argument, it will try to deconstruct the argument as if it had been passed in with a splat.

There are two kinds of Procs: lambdas and non-lambdas (confusingly, the latter are usually also called Procs). Lambdas behave like methods in terms of how the return keyword behaves and (more importantly, for this case) how they bind arguments, whereas non-lambda Procs behave like blocks in terms of how return and argument binding work. That's why Proc.new (which creates a non-lambda Proc) works, but lambda (which obviously creates a lambda) doesn't.

You can check whether a Proc is a lambda or not by calling Proc#lambda?.

If you want to deconstruct the argument, you will have to do so explicitly, the same way you would when you define a method:

lambda do |(key, value)|

And, yes, a more sane approach to argument binding for blocks, Procs and lambdas was one of the major backwards-incompatible changes in Ruby 1.9.



来源:https://stackoverflow.com/questions/5262738/inconsistency-of-arity-between-hash-each-and-lambdas

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!