Enumeration chains that give multiple arguments

I am trying to understand how Ruby handles enumerators that give several 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 select return the arguments as an array, whereas map gives them as two separate arguments?

+8
ruby block enumerators
source share
4 answers

It still follows from the discourse that we can analyze the source code, but we donโ€™t know whys. The core Ruby team is relatively responsive. I recommend that you log in to http://bugs.ruby-lang.org/issues/ and post a bug report there. They will probably consider this issue in a few weeks at most, and you can probably expect it to be fixed in the next minor version of Ruby. (That is, if there is no reason for development that we are not aware of, to keep things as they are.)

+2
source share

Try:

 a.each_with_index.map{|pr,last| p "pr: #{pr} last: #{last}"} 

map automatically deconstructs the values โ€‹โ€‹passed to it. The next question is why does he do this deconstruction and select not?

If you look at the source indicated on the Rdoc page for Array , they are almost identical, select differs only in that it does a test for the received value. There must be something else.

If we look at the source of Rubinius (mainly because I'm better with Ruby than C;) for map (aliased from collect ) shows:

 each do |*o| 

therefore, it separates the arguments in the path, while select (aliased from find_all ) does not:

 each do 

again, the design decision about why is outside of me. You will need to find out who wrote it, perhaps ask Matz :)


I have to add, again looking at the source of Rubinius, map actual signs on each and on yield , I donโ€™t understand why you would do both when you only need the output sign:

  each do |*o| ary << yield(*o) end 

whereas select does not work.

 each do o = Rubinius.single_block_arg ary << o if yield(o) end 
+4
source share

According to the MRI source , it seems that the iterator used in select uses its arguments, but map does not and passes them without packaging; the block in the 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 am sure that the ENUM_WANT_SVALUE() macro is used to turn the value passed into the block into the value of the splat array (as opposed to a tuple in which the last arguments are silently ignored). However, I do not know why it was designed in this way.

+2
source share

See the source of MRI at enum.c. As @PlatinumAzure said, magic happens in ENUM_WANT_SVALUE() :

 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; } 

And we can actually find this macro: do {i = rb_enum_values_pack(argc, argv);}while(0) .

So keep diving into the rb_enum_values_pack function:

 VALUE rb_enum_values_pack(int argc, VALUE *argv) { if (argc == 0) return Qnil; if (argc == 1) return argv[0]; return rb_ary_new4(argc, argv); } 

Cm? The arguments are packed on rb_ary_new4 , which is defined in array.c .

+2
source share

All Articles