W3cubDocs

/Ruby 2.7

class Enumerator::Lazy

Parent:
Enumerator

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

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]

Public Instance Methods

_enumerable_collect()
Alias for: collect
_enumerable_collect_concat()
Alias for: collect_concat
_enumerable_drop(p1)
Alias for: drop
_enumerable_drop_while()
Alias for: drop_while
_enumerable_filter()
Alias for: filter
_enumerable_filter_map()
Alias for: filter_map
_enumerable_find_all()
Alias for: find_all
_enumerable_flat_map()
Alias for: flat_map
_enumerable_grep(p1)
Alias for: grep
_enumerable_grep_v(p1)
Alias for: grep_v
_enumerable_map()
Alias for: map
_enumerable_reject()
Alias for: reject
_enumerable_select()
Alias for: select
_enumerable_take(p1)
Alias for: take
_enumerable_take_while()
Alias for: take_while
_enumerable_uniq()
Alias for: uniq
_enumerable_zip(*args)
Alias for: zip
chunk(*args) Show source
static VALUE
lazy_super(int argc, VALUE *argv, VALUE lazy)
{
    return enumerable_lazy(rb_call_super(argc, argv));
}

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

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

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

collect { |obj| block } → lazy_enumerator Show source
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);
}

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
collect_concat { |obj| block } → a_lazy_enumerator Show source
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_set_method(rb_block_call(rb_cLazy, id_new, 1, &obj,
                                         lazy_flat_map_proc, 0),
                           Qnil, 0);
}

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
drop(n) → lazy_enumerator Show source
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);
}

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

Also aliased as: _enumerable_drop
drop_while { |obj| block } → lazy_enumerator Show source
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);
}

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

Also aliased as: _enumerable_drop_while
eager → enum Show source
static VALUE
lazy_eager(VALUE self)
{
    return enumerator_init(enumerator_allocate(rb_cEnumerator),
                           self, sym_each, 0, 0, lazy_eager_size, Qnil, 0);
}

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

enum_for(method = :each, *args) → lazy_enum Show source
enum_for(method = :each, *args) {|*args| block } → lazy_enum
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, PASS_KW_SPLAT);
    if (rb_block_given_p()) {
        enumerator_ptr(lazy)->size = rb_block_proc();
    }
    return lazy;
}

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]
filter { |obj| block } → lazy_enumerator Show source
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);
}

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

Also aliased as: _enumerable_filter
filter_map { |obj| block } → lazy_enumerator Show source
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);
}

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]
Also aliased as: _enumerable_filter_map
find_all { |obj| block } → lazy_enumerator Show source
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);
}

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

Also aliased as: _enumerable_find_all
flat_map { |obj| block } → a_lazy_enumerator Show source
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_set_method(rb_block_call(rb_cLazy, id_new, 1, &obj,
                                         lazy_flat_map_proc, 0),
                           Qnil, 0);
}

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_flat_map
force()
Alias for: to_a
grep(pattern) → lazy_enumerator Show source
grep(pattern) { |obj| block } → lazy_enumerator
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);
}

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

Also aliased as: _enumerable_grep
grep_v(pattern) → lazy_enumerator Show source
grep_v(pattern) { |obj| block } → lazy_enumerator
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);
}

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

Also aliased as: _enumerable_grep_v
lazy → lazy_enumerator Show source
static VALUE
lazy_lazy(VALUE obj)
{
    return obj;
}

Returns self.

map { |obj| block } → lazy_enumerator Show source
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);
}

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_map
reject { |obj| block } → lazy_enumerator Show source
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);
}

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

Also aliased as: _enumerable_reject
select { |obj| block } → lazy_enumerator Show source
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);
}

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

Also aliased as: _enumerable_select
slice_after(*args) Show source
static VALUE
lazy_super(int argc, VALUE *argv, VALUE lazy)
{
    return enumerable_lazy(rb_call_super(argc, argv));
}

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

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

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

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

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

take(n) → lazy_enumerator Show source
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);
}

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

Also aliased as: _enumerable_take
take_while { |obj| block } → lazy_enumerator Show source
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);
}

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

Also aliased as: _enumerable_take_while
to_a → array Show source
force → array
static VALUE lazy_to_a(VALUE self)
{
}

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

Also aliased as: force
to_enum(method = :each, *args) → lazy_enum Show source
to_enum(method = :each, *args) {|*args| block } → lazy_enum
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, PASS_KW_SPLAT);
    if (rb_block_given_p()) {
        enumerator_ptr(lazy)->size = rb_block_proc();
    }
    return lazy;
}

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]
uniq → lazy_enumerator Show source
uniq { |item| block } → lazy_enumerator
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);
}

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

Also aliased as: _enumerable_uniq
with_index(offset = 0) {|(*args), idx| block } Show source
with_index(offset = 0)
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);
}

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.

zip(arg, ...) → lazy_enumerator Show source
zip(arg, ...) { |arr| block } → nil
static VALUE
lazy_zip(int argc, VALUE *argv, VALUE obj)
{
    VALUE ary, v;
    long i;
    rb_block_call_func *func = lazy_zip_arrays_func;

    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);
            func = lazy_zip_func;
            break;
        }
        rb_ary_push(ary, v);
    }

    return lazy_set_method(rb_block_call(rb_cLazy, id_new, 1, &obj,
                                         func, ary),
                           ary, lazy_receiver_size);
}

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

Also aliased as: _enumerable_zip

Private Instance Methods

with_index(offset = 0) {|(*args), idx| ... } Show source
with_index(offset = 0)
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));
}

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

Ruby Core © 1993–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.