class Enumerator::Lazy - RDoc Documentation

# class Enumerator::Lazy

`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 }
    # => #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:select>:drop(10)>:take_while>

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)
    #=> [21, 23]

    lazy.force
    #=> [21, 23, 25, 27, 29]

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       #=> #<Enumerator: [1, 2, 3]:map>
    [1, 2, 3].lazy.map  # ArgumentError: tried to call lazy map without a block

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'

    # This will fetch all URLs before selecting
    # necessary data
    URLS.map { |u| JSON.parse(open(u).read) }
      .select { |data| data.key?('stats') }
      .first(5)

    # This will fetch URLs one-by-one, only till
    # there is enough data to satisfy the condition
    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

    # This works lazily; if a checked item is found, it stops
    # iteration and does not look into remaining groups.
    first_checked = active_items.find(&:checked)

    # This returns an array of items like a normal enumerator does.
    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)
    #=> [4, 16, 36, 64, 100]

    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 }
    #=> #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:map>
    (1..Float::INFINITY).lazy.map {|i| i**2 }.first(3)
    #=> [1, 4, 9]

Alias for: collect
_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
    #=> ["f", "o", "o", "b", "a", "r"]

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

- `x` responds to both each and force, which means that `x` is a lazy enumerator.

- `x` is an array or responds to to_ary.

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

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

Alias for: collect_concat

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

Alias for: drop

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

Alias for: drop_while
_enumerable_filter()

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

Alias for: filter

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

    (1..).lazy.filter_map { |i| i * 2 if i.even? }.first(5)
    #=> [4, 8, 12, 16, 20]

Alias for: filter_map
_enumerable_find_all()

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

Alias for: find_all

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
    #=> ["f", "o", "o", "b", "a", "r"]

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

- `x` responds to both each and force, which means that `x` is a lazy enumerator.

- `x` is an array or responds to to_ary.

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

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

Alias for: flat_map

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

Alias for: grep

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

Alias for: grep_v

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

    (1..Float::INFINITY).lazy.map {|i| i**2 }
    #=> #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:map>
    (1..Float::INFINITY).lazy.map {|i| i**2 }.first(3)
    #=> [1, 4, 9]

Alias for: map

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

Alias for: reject

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

Alias for: select

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

Alias for: take

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

Alias for: take_while

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

Alias for: uniq

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));
    }

Also aliased as: slice_before, slice_after, slice_when, chunk_while
chunk_while(*args)

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

Alias for: chunk
collect { |obj| block } → lazy_enumerator

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

    (1..Float::INFINITY).lazy.map {|i| i**2 }
    #=> #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:map>
    (1..Float::INFINITY).lazy.map {|i| i**2 }.first(3)
    #=> [1, 4, 9]

Also aliased as: _enumerable_collect
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
    #=> ["f", "o", "o", "b", "a", "r"]

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

- `x` responds to both each and force, which means that `x` is a lazy enumerator.

- `x` is an array or responds to to_ary.

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

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

Also aliased as: _enumerable_collect_concat
Alias for: flat_map
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);
    }

Also aliased as: _enumerable_drop
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);
    }

Also aliased as: _enumerable_drop_while
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`:

    # See Object#to_enum for the definition of repeat
    r = 1..Float::INFINITY
    r.repeat(2).first(5) # => [1, 1, 2, 2, 3]
    r.repeat(2).class # => Enumerator
    r.repeat(2).map{|n| n ** 2}.first(5) # => endless loop!
    # works naturally on lazy enumerator:
    r.lazy.repeat(2).class # => Enumerator::Lazy
    r.lazy.repeat(2).map{|n| n ** 2}.first(5) # => [1, 1, 4, 4, 9]

Alias for: to_enum
filter { |obj| block } → lazy_enumerator

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

Also aliased as: _enumerable_filter
Alias for: select
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)
    #=> [4, 8, 12, 16, 20]

    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);
    }

Also aliased as: _enumerable_filter_map
find_all { |obj| block } → lazy_enumerator

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

Also aliased as: _enumerable_find_all
Alias for: select
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
    #=> ["f", "o", "o", "b", "a", "r"]

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

- `x` responds to both each and force, which means that `x` is a lazy enumerator.

- `x` is an array or responds to to_ary.

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

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

    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);
    }

Also aliased as: collect_concat, _enumerable_flat_map
force → array

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

Alias for: to_a
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);
    }

Also aliased as: _enumerable_grep
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);
    }

Also aliased as: _enumerable_grep_v
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 }
    #=> #<Enumerator::Lazy: #<Enumerator::Lazy: 1..Infinity>:map>
    (1..Float::INFINITY).lazy.map {|i| i**2 }.first(3)
    #=> [1, 4, 9]

    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);
    }

Also aliased as: collect, _enumerable_map
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);
    }

Also aliased as: _enumerable_reject
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);
    }

Also aliased as: find_all, filter, _enumerable_select
slice_after(*args)

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

Alias for: chunk
slice_before(*args)

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

Alias for: chunk
slice_when(*args)

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

Alias for: chunk
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);
    }

Also aliased as: _enumerable_take
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);
    }

Also aliased as: _enumerable_take_while
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`:

    # See Object#to_enum for the definition of repeat
    r = 1..Float::INFINITY
    r.repeat(2).first(5) # => [1, 1, 2, 2, 3]
    r.repeat(2).class # => Enumerator
    r.repeat(2).map{|n| n ** 2}.first(5) # => endless loop!
    # works naturally on lazy enumerator:
    r.lazy.repeat(2).class # => Enumerator::Lazy
    r.lazy.repeat(2).map{|n| n ** 2}.first(5) # => [1, 1, 4, 4, 9]

    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;
    }

Also aliased as: enum_for
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);
    }

Also aliased as: _enumerable_uniq
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);
    }

Also aliased as: _enumerable_zip

### 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));
    }