Why does Hash#select and Hash#reject pass a key to a unary block?

My understanding was that Hash#select and Hash#reject each passes an array of key and its value [key, value] as a single block argument for each iteration, and you can directly pick them separately within the block using implicit destructive assignment:

{a: 1, b: 2}.select{|k, v| k == :a} # => {:a => 1}
{a: 1, b: 2}.reject{|k, v| v == 1} # => {:b => 2}

or explicit destructive assignment:

{a: 1, b: 2}.select{|(k, v)| k == :a} # => {:a => 1}

I expected that, when I pass a unary block, the whole [key, value] array would be passed, but in reality, it seems like the key is passed:

{a: 1}.select{|e| p e} # => Prints `:a`  (I expected `[:a, 1]`)

Why does it work this way? For other Hash instance methods like map , the whole [key, value] array is passed.

If it were especially designed to work differently for unary blocks as compared to binary blocks, then I can understand it is useful. But, then I would not understand why the case above with explicit destructive assignment works as is. And I also do not find any document mentioning such specification.

Edit I had a wrong result for {a: 1, b: 2}.reject{|(k, v)| v == 1} {a: 1, b: 2}.reject{|(k, v)| v == 1} . It is corrected here:

{a: 1, b: 2}.reject{|(k, v)| v == 1} # => {:a=>1, :b=>2} (not `{:b=>2}`)

Now, this also indicates that (k, v) is the key , not [key, value] , so v is always nil . Cf. Darek Nędza's comment.


It's actually passing two arguments, always.

What you're observing is merely the difference between how procs and lambdas treat excess arguments. Blocks (Procs unless you tell Ruby otherwise) behave as if it had an extra splat and discard excess arguments, whereas lambdas (and method objects) reject the caller due to the incorrect arity.

Demonstration:

>> p = proc { |e| p e }
=> #<Proc:0x007f8dfa1c8b50@(irb):1>
>> l = lambda { |e| p e }
=> #<Proc:0x007f8dfa838620@(irb):2 (lambda)>
>> {a: 1}.select &p
:a
=> {:a=>1}
>> {a: 1}.select &l
ArgumentError: wrong number of arguments (2 for 1)
    from (irb):2:in `block in irb_binding'
    from (irb):4:in `select'
    from (irb):4
    from /usr/local/bin/irb:11:in `<main>'

As an aside, since it was mentioned in the comments: map , in contrast, actually passes one argument. It gets allocated to two different variables because you can assign multiple variables with an array on the right side of the assignment operator, but it's really one argument all along.

Demonstration:

>> {a: 1}.map { |k, v| p k, v }
:a
1
>> {a: 1}.map &p
[:a, 1]
=> [[:a, 1]]
>> {a: 1}.map &l
[:a, 1]

And upon changing p and l defined further up:

>> p = proc { |k, v| p k, v }
=> #<Proc:0x007ffd94089258@(irb):1>
>> l = lambda { |k, v| p k, v }
=> #<Proc:0x007ffd940783e0@(irb):2 (lambda)>
>> {a: 1}.map &p
:a
1
=> [[:a, 1]]
>> {a: 1}.map &l
ArgumentError: wrong number of arguments (1 for 2)
    from (irb):2:in `block in irb_binding'
    from (irb):4:in `each'
    from (irb):4:in `map'
    from (irb):4
    from /usr/local/bin/irb:11:in `<main>'
链接地址: http://www.djcxy.com/p/60958.html

上一篇: 在不使用初始化方法的情况下将哈希传递给类

下一篇: 为什么Hash#select和Hash#reject将一个键传递给一元块?