我试图弄清楚 Ruby 如何处理产生多个参数的链式枚举器。看看这个片段:
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"
为什么select
将参数作为数组map
生成,而将它们作为两个单独的参数生成?
我试图弄清楚 Ruby 如何处理产生多个参数的链式枚举器。看看这个片段:
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"
为什么select
将参数作为数组map
生成,而将它们作为两个单独的参数生成?
尝试:
a.each_with_index.map{|pr,last| p "pr: #{pr} last: #{last}"}
map
自动解构传递给它的值。下一个问题是它为什么要进行这种解构而select
不是?
如果您查看Array 的 Rdoc 页面上给出的源代码,它们实际上是相同的,select
只是不同之处在于它对产生的值进行了测试。其他地方肯定发生了什么事。
如果我们查看Rubinius 源代码(主要是因为我使用 Ruby 比使用 C 更好;)因为map
(别名来自collect
)它向我们展示了:
each do |*o|
所以它在通过的过程中会喷溅参数,而select (aliased from find_all
) 不会:
each do
再次,关于为什么的设计决定超出了我的范围。你必须找出是谁写的,也许问问 Matz :)
我应该补充一点,再次查看 Rubinius 源代码,map
实际 splats oneach
和on yield
,我不明白为什么你会在只需要 yield splat 时同时做这两个:
each do |*o|
ary << yield(*o)
end
而select
没有。
each do
o = Rubinius.single_block_arg
ary << o if yield(o)
end
根据MRI source,似乎在select
splats 中使用的迭代器传入了它的参数,但map
没有传入并将它们解包传递;后一种情况下的块默默地忽略其他参数。
中使用的迭代器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;
}
中使用的迭代器map
:
static VALUE
collect_i(VALUE i, VALUE ary, int argc, VALUE *argv)
{
rb_ary_push(ary, enum_yield(argc, argv));
return Qnil;
}
我很确定ENUM_WANT_SVALUE()
宏用于将传递给块的值转换为一个 splat 数组值(而不是一个元组,后者的参数被默默地忽略)。也就是说,我不知道为什么它是这样设计的。
从目前的论述来看,我们可以分析源代码,但我们不知道原因。Ruby 核心团队相对来说反应灵敏。我建议您登录http://bugs.ruby-lang.org/issues/并在那里发布错误报告。他们肯定最多会在几周内看到这个问题,您可能会期望它在下一个 Ruby 小版本中得到纠正。(也就是说,除非有我们不知道的设计原理来保持原样。)
让我们在enum.c中查看 MRI 源代码。正如@PlatinumAzure 所说,魔法发生在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;
}
而我们可以发现这个宏其实是:do {i = rb_enum_values_pack(argc, argv);}while(0)
.
所以让我们继续深入了解rb_enum_values_pack
函数:
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);
}
看?参数由在array.crb_ary_new4
中定义的打包。