ruby Enumerable#first vs #take

时间:2015-02-09 19:21:30

标签: ruby

ruby​​ Enumerable / Array first(n)take(n)之间的区别是什么?

我模糊地回忆起take与懒惰评估有关,但我无法弄清楚如何使用它来做到这一点,并且找不到任何有用的谷歌搜索或文档。 "取"是google的硬方法名称。

first(n)take(n) documented完全相同,不太有帮助。

first → obj or nil
first(n) → an_array
Returns the first element, or the first n elements, of the enumerable. If the   enumerable is empty, the first form returns nil, and the second form returns an empty array.

-

take(n) → array
Returns first n elements from enum.

告诉我"采取与懒惰评估有关的事情"还不够,我有点记得,我需要一个如何使用它的例子,与first相比。

1 个答案:

答案 0 :(得分:5)

好吧,我看过源代码(Ruby 2.1.5)。 引擎,如果为first提供了参数,则会将其转发到take。否则,它返回一个值:

static VALUE
enum_first(int argc, VALUE *argv, VALUE obj)
{
    NODE *memo;
    rb_check_arity(argc, 0, 1);
    if (argc > 0) {
    return enum_take(obj, argv[0]);
    }
    else {
    memo = NEW_MEMO(Qnil, 0, 0);
    rb_block_call(obj, id_each, 0, 0, first_i, (VALUE)memo);
    return memo->u1.value;
    }
}
另一方面,

take 需要一个参数并始终返回给定大小或更小的数组,其中包含从头开始的元素。

static VALUE
enum_take(VALUE obj, VALUE n)
{
    NODE *memo;
    VALUE result;
    long len = NUM2LONG(n);

    if (len < 0) {
    rb_raise(rb_eArgError, "attempt to take negative size");
    }

    if (len == 0) return rb_ary_new2(0);
    result = rb_ary_new2(len);
    memo = NEW_MEMO(result, 0, len);
    rb_block_call(obj, id_each, 0, 0, take_i, (VALUE)memo);
    return result;
}

是的,这就是为什么这两者如此相似的原因。唯一的区别似乎是,first可以不带参数调用,不会输出数组,而是输出单值。另一方面,<...>.first(1)相当于<...>.take(1)。就这么简单。

然而,对于懒惰的集合,情况有所不同。懒惰集合中的first仍为enum_first,如上所示,它是enum_take的快捷方式。但是,take是C编码lazy_take

static VALUE
lazy_take(VALUE obj, VALUE n)
{
    long len = NUM2LONG(n);
    VALUE lazy;

    if (len < 0) {
    rb_raise(rb_eArgError, "attempt to take negative size");
    }
    if (len == 0) {
    VALUE len = INT2FIX(0);
    lazy = lazy_to_enum_i(obj, sym_cycle, 1, &len, 0);
    }
    else {
    lazy = rb_block_call(rb_cLazy, id_new, 1, &obj,
                     lazy_take_func, n);
    }
    return lazy_set_method(lazy, rb_ary_new3(1, n), lazy_take_size);
}

...没有立即评估,需要.force来电。{/ p>

事实上,它已暗示in the docs under lazy,它列出了所有懒惰实施的方法。该列表包含take,但不包含first。也就是说,懒惰序列take保持懒惰而first不会。

以下是一个示例,它们的工作方式不同:

lz = (1..Float::INFINITY).lazy.map{|i| i }
# An infinite sequence, evaluating it head-on won't do
# Ruby 2.2 also offers `.map(&:itself)`

lz.take(5)                                                                                                                       
#=> #<Enumerator::Lazy: ...>
# Well, `take` is lazy then
# Still, we need values

lz.take(5).force
#=> [1, 2, 3, 4, 5]
# Why yes, values, finally

lz.first(5)
#=> [1, 2, 3, 4, 5]
# So `first` is not lazy, it evaluates values immediately

通过在2.2之前的版本中运行并使用2.2(<...>.lazy.map(&:itself))的代码可以获得一些额外的乐趣,因为在你失去懒惰的那一刻,这会立即引发NoMethodError