好吧,我已经查看了源代码(Ruby 2.1.5)。在引擎盖下, if 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
呼吁这一点。
而事实上,它已经暗示了在下面的文档中lazy http://ruby-doc.org/core-2.2.0/Enumerable.html#method-i-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
.