Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chaining enumerators that yield multiple arguments

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?

like image 604
Ben Weissmann Avatar asked Jan 24 '13 02:01

Ben Weissmann


1 Answers

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.

like image 192
Platinum Azure Avatar answered Sep 30 '22 04:09

Platinum Azure