I'm trying to figure out how Ruby handles chaining enumerators that yield multiple arguments. Take a look at this snippet:
a = ['a', 'b', 'c']
a.each_with_index.select{|pr| p pr}
# prints:
# ["a", 0]
# ["b", 1]
# ["c", 2]
a.each_with_index.map{|pr| p pr}
# prints:
# "a"
# "b"
# "c"
Why does select
yield the arguments as an array, whereas map
yields them as two separate arguments?
According to the MRI source, it seems like the iterator used in select
splats its arguments coming in, but map
does not and passes them unpacked; the block in your latter case silently ignores the other arguments.
The iterator used in select
:
static VALUE
find_all_i(VALUE i, VALUE ary, int argc, VALUE *argv)
{
ENUM_WANT_SVALUE();
if (RTEST(rb_yield(i))) {
rb_ary_push(ary, i);
}
return Qnil;
}
The iterator used in map
:
static VALUE
collect_i(VALUE i, VALUE ary, int argc, VALUE *argv)
{
rb_ary_push(ary, enum_yield(argc, argv));
return Qnil;
}
I'm pretty sure the ENUM_WANT_SVALUE()
macro is used to turn the value passed into the block into a splat array value (as opposed to a tuple with the latter arguments silently ignored). That said, I don't know why it was designed this way.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With