ruby Enumerable#first vs #take

jro*_*ind 4 ruby

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

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

first(n)take(n)记录的漂亮相同,没有太大的帮助.

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.
Run Code Online (Sandbox Code Playgroud)

-

take(n) ? array
Returns first n elements from enum.
Run Code Online (Sandbox Code Playgroud)

告诉我"与懒惰的评价有关"是不够的,我有点记得已经,我需要一个如何使用它的例子,相比之下first.

D-s*_*ide 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

所以,是的,这就是为什么这两个如此相似的原因.唯一的区别似乎是,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);
}
Run Code Online (Sandbox Code Playgroud)

...不会立即评估,需要.force打电话.

事实上,它在文档中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
Run Code Online (Sandbox Code Playgroud)

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