Neither each
not yield
are doing anything special here, that's just how block arguments work. Consider this simple example:
def f(x) yield x end
and now we can see what happens:
>> f([1,2]) { |a| puts a.inspect }
[1, 2]
>> f([1,2]) { |a, b| puts "#{a} - #{b}" }
1 - 2
>> f([1,2]) { |a, b, c| puts "#{a} - #{b} - #{c}" }
1 - 2 -
You'll see similar destructing in assignments:
a, b = [1, 2]
You can also do it explicitly with a splat:
a, b = *[1, 2]
or like this:
def g(x) yield *x end
g([1, 2]) { |a, b| puts "#{a} - #{b}" }
Presumably the block knows what sorts of things it will be given so the block is well positioned to unpack the arguments. Note that the g
function has to know that its argument is splatable (i.e. an array) but f
doesn't. f
nicely puts the "what sort of thing is x
" logic together in the call to f
, g
buries half of the logic inside itself. One place where the difference becomes apparent is when you use Enumerable methods on a Hash:
{ :where => :is, :pancakes => :house? }.map { |k, v| ... }
Enumerable#map
doesn't need to know that a Hash works in key/value two element arrays, it just passes things around and leaves it up everyone else to worry about the details.