问题
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 Proc
s: lambdas and non-lambdas (confusingly, the latter are usually also called Proc
s). 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 Proc
s 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, Proc
s 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