9

我试图弄清楚 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生成,而将它们作为两个单独的参数生成?

4

4 回答 4

5

尝试:

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
于 2013-01-24T03:17:31.887 回答
4

根据MRI source,似乎在selectsplats 中使用的迭代器传入了它的参数,但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 数组值(而不是一个元组,后者的参数被默默地忽略)。也就是说,我不知道为什么它是这样设计的。

于 2013-01-24T03:24:54.633 回答
3

从目前的论述来看,我们可以分析源代码,但我们不知道原因。Ruby 核心团队相对来说反应灵敏。我建议您登录http://bugs.ruby-lang.org/issues/并在那里发布错误报告。他们肯定最多会在几周内看到这个问题,您可能会期望它在下一个 Ruby 小版本中得到纠正。(也就是说,除非有我们不知道的设计原理来保持原样。)

于 2013-01-24T04:36:42.720 回答
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);
}

看?参数由在array.crb_ary_new4中定义的打包。

于 2013-01-24T03:36:07.023 回答