class Enumerator::Lazy - RDoc Documentation (original) (raw)

Enumerator::Lazy is a special type of Enumerator, that allows constructing chains of operations without evaluating them immediately, and evaluating values on as-needed basis. In order to do so it redefines most of Enumerable methods so that they just construct another lazy enumerator.

Enumerator::Lazy can be constructed from any Enumerable with the Enumerable#lazy method.

lazy = (1..Float::INFINITY).lazy.select(&:odd?).drop(10).take_while { |i| i < 30 }

The real enumeration is performed when any non-redefined Enumerable method is called, like Enumerable#first or Enumerable#to_a (the latter is aliased as force for more semantic code):

lazy.first(2)

lazy.force

Note that most Enumerable methods that could be called with or without a block, on Enumerator::Lazy will always require a block:

[1, 2, 3].map
[1, 2, 3].lazy.map

This class allows idiomatic calculations on long or infinite sequences, as well as chaining of calculations without constructing intermediate arrays.

Example for working with a slowly calculated sequence:

require 'open-uri'

URLS.map { |u| JSON.parse(open(u).read) } .select { |data| data.key?('stats') } .first(5)

URLS.lazy.map { |u| JSON.parse(open(u).read) } .select { |data| data.key?('stats') } .first(5)

Ending a chain with “.eager” generates a non-lazy enumerator, which is suitable for returning or passing to another method that expects a normal enumerator.

def active_items groups .lazy .flat_map(&:items) .reject(&:disabled) .eager end

first_checked = active_items.find(&:checked)

all_checked = active_items.select(&:checked)

Public Class Methods

new(obj, size=nil) { |yielder, *values| block } click to toggle source

Creates a new Lazy enumerator. When the enumerator is actually enumerated (e.g. by calling force), obj will be enumerated and each value passed to the given block. The block can yield values back using yielder. For example, to create a “filter+map” enumerator:

def filter_map(sequence) Lazy.new(sequence) do |yielder, *values| result = yield *values yielder << result if result end end

filter_map(1..Float::INFINITY) {|i| i*i if i.even?}.first(5)

static VALUE lazy_initialize(int argc, VALUE *argv, VALUE self) { VALUE obj, size = Qnil; VALUE generator;

rb_check_arity(argc, 1, 2);
if (!rb_block_given_p()) {
    rb_raise(rb_eArgError, "tried to call lazy new without a block");
}
obj = argv[0];
if (argc > 1) {
    size = argv[1];
}
generator = generator_allocate(rb_cGenerator);
rb_block_call(generator, id_initialize, 0, 0, lazy_init_block_i, obj);
enumerator_init(self, generator, sym_each, 0, 0, 0, size, 0);
rb_ivar_set(self, id_receiver, obj);

return self;

}

Public Instance Methods

_enumerable_collect()

Like Enumerable#map, but chains operation to be lazy-evaluated.

(1..Float::INFINITY).lazy.map {|i| i**2 }

(1..Float::INFINITY).lazy.map {|i| i**2 }.first(3)

_enumerable_collect_concat()

Returns a new lazy enumerator with the concatenated results of running block once for every element in the lazy enumerator.

["foo", "bar"].lazy.flat_map {|i| i.each_char.lazy}.force

A value x returned by block is decomposed if either of the following conditions is true:

Otherwise, x is contained as-is in the return value.

[{a:1}, {b:2}].lazy.flat_map {|i| i}.force

Like Enumerable#filter_map, but chains operation to be lazy-evaluated.

(1..).lazy.filter_map { |i| i * 2 if i.even? }.first(5)

Returns a new lazy enumerator with the concatenated results of running block once for every element in the lazy enumerator.

["foo", "bar"].lazy.flat_map {|i| i.each_char.lazy}.force

A value x returned by block is decomposed if either of the following conditions is true:

Otherwise, x is contained as-is in the return value.

[{a:1}, {b:2}].lazy.flat_map {|i| i}.force

Like Enumerable#map, but chains operation to be lazy-evaluated.

(1..Float::INFINITY).lazy.map {|i| i**2 }

(1..Float::INFINITY).lazy.map {|i| i**2 }.first(3)

Alias for: map

Like Enumerable#zip, but chains operation to be lazy-evaluated. However, if a block is given to zip, values are enumerated immediately.

Alias for: zip

chunk(*args) click to toggle source

Like Enumerable#chunk, but chains operation to be lazy-evaluated.

static VALUE lazy_super(int argc, VALUE *argv, VALUE lazy) { return enumerable_lazy(rb_call_super(argc, argv)); }

collect { |obj| block } → lazy_enumerator

Like Enumerable#map, but chains operation to be lazy-evaluated.

(1..Float::INFINITY).lazy.map {|i| i**2 }

(1..Float::INFINITY).lazy.map {|i| i**2 }.first(3)

Alias for: map

collect_concat { |obj| block } → a_lazy_enumerator

Returns a new lazy enumerator with the concatenated results of running block once for every element in the lazy enumerator.

["foo", "bar"].lazy.flat_map {|i| i.each_char.lazy}.force

A value x returned by block is decomposed if either of the following conditions is true:

Otherwise, x is contained as-is in the return value.

[{a:1}, {b:2}].lazy.flat_map {|i| i}.force

drop(n) → lazy_enumerator click to toggle source

Like Enumerable#drop, but chains operation to be lazy-evaluated.

static VALUE lazy_drop(VALUE obj, VALUE n) { long len = NUM2LONG(n); VALUE argv[2]; argv[0] = sym_each; argv[1] = n;

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

return lazy_add_method(obj, 2, argv, n, rb_ary_new3(1, n), &lazy_drop_funcs);

}

drop_while { |obj| block } → lazy_enumerator click to toggle source

Like Enumerable#drop_while, but chains operation to be lazy-evaluated.

static VALUE lazy_drop_while(VALUE obj) { if (!rb_block_given_p()) { rb_raise(rb_eArgError, "tried to call lazy drop_while without a block"); }

return lazy_add_method(obj, 0, 0, Qfalse, Qnil, &lazy_drop_while_funcs);

}

eager → enum click to toggle source

Returns a non-lazy Enumerator converted from the lazy enumerator.

static VALUE lazy_eager(VALUE self) { return enumerator_init(enumerator_allocate(rb_cEnumerator), self, sym_each, 0, 0, lazy_eager_size, Qnil, 0); }

enum_for(method = :each, *args) → lazy_enum

enum_for(method = :each, *args) {|*args| block } → lazy_enum

Similar to Object#to_enum, except it returns a lazy enumerator. This makes it easy to define Enumerable methods that will naturally remain lazy if called from a lazy enumerator.

For example, continuing from the example in Object#to_enum:

r = 1..Float::INFINITY r.repeat(2).first(5) r.repeat(2).class r.repeat(2).map{|n| n ** 2}.first(5)

r.lazy.repeat(2).class r.lazy.repeat(2).map{|n| n ** 2}.first(5)

filter { |obj| block } → lazy_enumerator

filter_map { |obj| block } → lazy_enumerator click to toggle source

Like Enumerable#filter_map, but chains operation to be lazy-evaluated.

(1..).lazy.filter_map { |i| i * 2 if i.even? }.first(5)

static VALUE lazy_filter_map(VALUE obj) { if (!rb_block_given_p()) { rb_raise(rb_eArgError, "tried to call lazy filter_map without a block"); }

return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_filter_map_funcs);

}

find_all { |obj| block } → lazy_enumerator

flat_map { |obj| block } → a_lazy_enumerator click to toggle source

Returns a new lazy enumerator with the concatenated results of running block once for every element in the lazy enumerator.

["foo", "bar"].lazy.flat_map {|i| i.each_char.lazy}.force

A value x returned by block is decomposed if either of the following conditions is true:

Otherwise, x is contained as-is in the return value.

[{a:1}, {b:2}].lazy.flat_map {|i| i}.force

static VALUE lazy_flat_map(VALUE obj) { if (!rb_block_given_p()) { rb_raise(rb_eArgError, "tried to call lazy flat_map without a block"); }

return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_flat_map_funcs);

}

grep(pattern) → lazy_enumerator click to toggle source

grep(pattern) { |obj| block } → lazy_enumerator

Like Enumerable#grep, but chains operation to be lazy-evaluated.

static VALUE lazy_grep(VALUE obj, VALUE pattern) { const lazyenum_funcs *const funcs = rb_block_given_p() ? &lazy_grep_iter_funcs : &lazy_grep_funcs; return lazy_add_method(obj, 0, 0, pattern, rb_ary_new3(1, pattern), funcs); }

grep_v(pattern) → lazy_enumerator click to toggle source

grep_v(pattern) { |obj| block } → lazy_enumerator

Like Enumerable#grep_v, but chains operation to be lazy-evaluated.

static VALUE lazy_grep_v(VALUE obj, VALUE pattern) { const lazyenum_funcs *const funcs = rb_block_given_p() ? &lazy_grep_v_iter_funcs : &lazy_grep_v_funcs; return lazy_add_method(obj, 0, 0, pattern, rb_ary_new3(1, pattern), funcs); }

lazy → lazy_enumerator click to toggle source

Returns self.

static VALUE lazy_lazy(VALUE obj) { return obj; }

map { |obj| block } → lazy_enumerator click to toggle source

Like Enumerable#map, but chains operation to be lazy-evaluated.

(1..Float::INFINITY).lazy.map {|i| i**2 }

(1..Float::INFINITY).lazy.map {|i| i**2 }.first(3)

static VALUE lazy_map(VALUE obj) { if (!rb_block_given_p()) { rb_raise(rb_eArgError, "tried to call lazy map without a block"); }

return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_map_funcs);

}

reject { |obj| block } → lazy_enumerator click to toggle source

Like Enumerable#reject, but chains operation to be lazy-evaluated.

static VALUE lazy_reject(VALUE obj) { if (!rb_block_given_p()) { rb_raise(rb_eArgError, "tried to call lazy reject without a block"); }

return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_reject_funcs);

}

select { |obj| block } → lazy_enumerator click to toggle source

Like Enumerable#select, but chains operation to be lazy-evaluated.

static VALUE lazy_select(VALUE obj) { if (!rb_block_given_p()) { rb_raise(rb_eArgError, "tried to call lazy select without a block"); }

return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_select_funcs);

}

take(n) → lazy_enumerator click to toggle source

Like Enumerable#take, but chains operation to be lazy-evaluated.

static VALUE lazy_take(VALUE obj, VALUE n) { long len = NUM2LONG(n); int argc = 0; VALUE argv[2];

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

if (len == 0) {
   argv[0] = sym_cycle;
   argv[1] = INT2NUM(0);
   argc = 2;
}

return lazy_add_method(obj, argc, argv, n, rb_ary_new3(1, n), &lazy_take_funcs);

}

take_while { |obj| block } → lazy_enumerator click to toggle source

Like Enumerable#take_while, but chains operation to be lazy-evaluated.

static VALUE lazy_take_while(VALUE obj) { if (!rb_block_given_p()) { rb_raise(rb_eArgError, "tried to call lazy take_while without a block"); }

return lazy_add_method(obj, 0, 0, Qnil, Qnil, &lazy_take_while_funcs);

}

to_a → array click to toggle source

Expands lazy enumerator to an array. See Enumerable#to_a.

static VALUE lazy_to_a(VALUE self) { }

Also aliased as: force

to_enum(method = :each, *args) → lazy_enum click to toggle source

to_enum(method = :each, *args) {|*args| block } → lazy_enum

Similar to Object#to_enum, except it returns a lazy enumerator. This makes it easy to define Enumerable methods that will naturally remain lazy if called from a lazy enumerator.

For example, continuing from the example in Object#to_enum:

r = 1..Float::INFINITY r.repeat(2).first(5) r.repeat(2).class r.repeat(2).map{|n| n ** 2}.first(5)

r.lazy.repeat(2).class r.lazy.repeat(2).map{|n| n ** 2}.first(5)

static VALUE lazy_to_enum(int argc, VALUE *argv, VALUE self) { VALUE lazy, meth = sym_each, super_meth;

if (argc > 0) {
    --argc;
    meth = *argv++;
}
if (RTEST((super_meth = rb_hash_aref(lazy_use_super_method, meth)))) {
    meth = super_meth;
}
lazy = lazy_to_enum_i(self, meth, argc, argv, 0, rb_keyword_given_p());
if (rb_block_given_p()) {
    enumerator_ptr(lazy)->size = rb_block_proc();
}
return lazy;

}

uniq → lazy_enumerator click to toggle source

uniq { |item| block } → lazy_enumerator

Like Enumerable#uniq, but chains operation to be lazy-evaluated.

static VALUE lazy_uniq(VALUE obj) { const lazyenum_funcs *const funcs = rb_block_given_p() ? &lazy_uniq_iter_funcs : &lazy_uniq_funcs; return lazy_add_method(obj, 0, 0, Qnil, Qnil, funcs); }

with_index(offset = 0) {|(*args), idx| block } click to toggle source

with_index(offset = 0)

If a block is given, iterates the given block for each element with an index, which starts from offset, and returns a lazy enumerator that yields the same values (without the index).

If a block is not given, returns a new lazy enumerator that includes the index, starting from offset.

offset

the starting index to use

See Enumerator#with_index.

static VALUE lazy_with_index(int argc, VALUE *argv, VALUE obj) { VALUE memo;

rb_scan_args(argc, argv, "01", &memo);
if (NIL_P(memo))
    memo = LONG2NUM(0);

return lazy_add_method(obj, 0, 0, memo, rb_ary_new_from_values(1, &memo), &lazy_with_index_funcs);

}

zip(arg, ...) → lazy_enumerator click to toggle source

zip(arg, ...) { |arr| block } → nil

Like Enumerable#zip, but chains operation to be lazy-evaluated. However, if a block is given to zip, values are enumerated immediately.

static VALUE lazy_zip(int argc, VALUE *argv, VALUE obj) { VALUE ary, v; long i; const lazyenum_funcs *funcs = &lazy_zip_funcs[1];

if (rb_block_given_p()) {
    return rb_call_super(argc, argv);
}

ary = rb_ary_new2(argc);
for (i = 0; i < argc; i++) {
    v = rb_check_array_type(argv[i]);
    if (NIL_P(v)) {
        for (; i < argc; i++) {
            if (!rb_respond_to(argv[i], id_each)) {
                rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (must respond to :each)",
                         rb_obj_class(argv[i]));
            }
        }
        ary = rb_ary_new4(argc, argv);
        funcs = &lazy_zip_funcs[0];
        break;
    }
    rb_ary_push(ary, v);
}

return lazy_add_method(obj, 0, 0, ary, ary, funcs);

}

Private Instance Methods

with_index(offset = 0) {|(*args), idx| ... } click to toggle source

with_index(offset = 0)

Iterates the given block for each element with an index, which starts from offset. If no block is given, returns a new Enumerator that includes the index, starting from offset

offset

the starting index to use

static VALUE enumerator_with_index(int argc, VALUE *argv, VALUE obj) { VALUE memo;

rb_check_arity(argc, 0, 1);
RETURN_SIZED_ENUMERATOR(obj, argc, argv, enumerator_enum_size);
memo = (!argc || NIL_P(memo = argv[0])) ? INT2FIX(0) : rb_to_int(memo);
return enumerator_block_call(obj, enumerator_with_index_i, (VALUE)MEMO_NEW(memo, 0, 0));

}