我正在试图弄清楚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
将它们作为两个单独的参数生成?
答案 0 :(得分:5)
尝试:
a.each_with_index.map{|pr,last| p "pr: #{pr} last: #{last}"}
map
会自动解构传递给它的值。接下来的问题是为什么它正在进行解构而select
不是?
如果你看the source given on the Rdoc page for Array它们实际上是相同的,select
只是区别在于它对产生的值进行了测试。必须在其他地方发生一些事情。
如果我们看map
(主要是因为我对Ruby比C更好;)collect
(来自each do |*o|
的别名),它会向我们展示:
find_all
所以它正在通过的方式展开论据,而Rubinius source(来自each do
的别名)不会:
map
再次,关于为什么的设计决定超出了我的范围。你必须找出是谁写的,也许问马茨:)
我应该添加,再次查看Rubinius来源,each
yield
上的 each do |*o|
ary << yield(*o)
end
和 select
上的实际splats,我不明白为什么你' d只在需要收益率时才做两件事:
each do
o = Rubinius.single_block_arg
ary << o if yield(o)
end
而{{1}}没有。
{{1}}
答案 1 :(得分:4)
根据the 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数组值(而不是以后面的参数静默忽略的元组)。也就是说,我不知道为什么会这样设计。
答案 2 :(得分:3)
让我们在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);
}
请参阅?参数由rb_ary_new4
打包,{{1}}定义。
答案 3 :(得分:3)
到目前为止,从话语中我们可以分析源代码,但我们不知道为什么。 Ruby核心团队的响应速度相对较快。我建议您登录http://bugs.ruby-lang.org/issues/并在那里发布错误报告。他们肯定会在几周内查看这个问题,你可能会期望在下一个次要版本的Ruby中纠正它。 (也就是说,除非我们不知道设计原理以保持原样。)