NAME
Sidef::Object::Lazy - Lazy evaluation wrapper that computes values only when needed.
DESCRIPTION
This class implements lazy evaluation for iterable objects in Sidef. A Lazy object wraps another iterable (anything with an iter method -- ranges, arrays, file handles, and so on) together with a pipeline of pending operations. Nothing is computed until a terminal method is called; values are then pulled one at a time from the wrapped iterable and pushed through the whole pipeline before the next one is pulled, without ever materializing an intermediate array in memory.
Methods split into two kinds:
Intermediate methods ("grep", "select", "map", "collect") don't run anything -- they return a new
Lazyobject with one more step appended to the pipeline, so they can be chained freely.Terminal methods (everything else: "each", "to_a", "first", "nth", "sum", "reduce", and so on) actually drive the iteration and produce a concrete result.
At each step of the pipeline, only a single value is in flight -- blocks passed to "grep" and "map" each receive (and, for map, must return) one value at a time, not a list.
SYNOPSIS
# Create a lazy iterator from a range
say((^Inf).lazy.grep { .is_prime }.first(10)) # first 10 primes
# Lazy processing of file lines
File("data.txt").open_r.lazy.grep { .match(/pattern/) }.map { .uc }.first(5)
# Lazy array processing
say([1, 2, 3, 4, 5].lazy.grep { .is_even }.map { _**2 }.to_a)
INHERITS
Inherits methods from Sidef::Object::Object.
CONSTRUCTION
new
Lazy.new(obj: iterable)
Creates a new Lazy object wrapping iterable (anything with an iter method), with an empty pipeline. This is typically not called directly; instead, call .lazy on the object you want to iterate lazily (see "lazy" in Sidef::Object::Object), or use "lazy" below to get an existing Lazy object back unchanged.
ITERATION
each
self.each(block)
Drives the pipeline to completion, calling block once per resulting element (for side effects), and returns self.
(1..10).lazy.grep { .is_prime }.each {|p| say(p) }
# Prints: 2, 3, 5, 7
iter
self.iter
Returns a Block (an iterator) that, each time it's called, pulls the next raw value from the wrapped iterable, runs it through the pipeline (skipping over any values filtered out by a grep step), and returns the result -- or nil once the underlying iterable is exhausted.
var it = (1..5).lazy.map {|n| n**2 }.iter
say(it.run) #=> 1
say(it.run) #=> 4
say(it.run) #=> 9
to_a
self.to_a
Drives the pipeline to completion and collects every resulting element into an Array. Use with caution on an infinite or very large underlying sequence, since this method never stops on its own.
say((1..5).lazy.map {|n| n**2 }.to_a) #=> [1, 4, 9, 16, 25]
lazy
self.lazy
Returns self unchanged. This method is idempotent and exists so that .lazy can always be chained safely, even on a value that's already a Lazy object.
var lz = (1..10).lazy.lazy # same as (1..10).lazy
FILTERING AND TRANSFORMATION
grep
self.grep(block)
Returns a new Lazy object with a filtering step appended to the pipeline: only elements for which block returns a truthy value are kept. Nothing is evaluated until a terminal method is called on the result.
say((1..100).lazy.grep {|n| n.is_even }.first(5)) #=> [2, 4, 6, 8, 10]
Aliases: select
map
self.map(block)
Returns a new Lazy object with a transformation step appended to the pipeline: each element is replaced with the result of calling block on it. Nothing is evaluated until a terminal method is called on the result.
say((1..5).lazy.map {|n| n**2 }.to_a) #=> [1, 4, 9, 16, 25]
Aliases: collect
while
self.while(block)
Unlike "grep" and "map", this is a terminal method: it immediately drives the pipeline, collecting elements into an Array for as long as block returns a truthy value, and stops (without including the element that failed) as soon as block returns a falsy value.
say((^Inf).lazy.map {|n| n**2 }.while {|n| n < 100 })
#=> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
SEARCHING
first
self.first
self.first(n)
self.first(block)
With no argument, drives the pipeline just far enough to produce a single value and returns it directly (not wrapped in an array), or nil if the sequence is empty. With a number n, returns an Array of the first n resulting elements (an empty Array immediately if n <= 0, without touching the iterator at all). With a block, delegates entirely to "first_by".
say((^Inf).lazy.grep {|n| n.is_prime }.first(5)) #=> [2, 3, 5, 7, 11]
say((^Inf).lazy.grep {|n| n.is_prime }.first) #=> 2
say((1..10).lazy.first(0)) #=> []
first_by
self.first_by(block)
Returns the first element for which block returns a truthy value, or nil if no such element exists. Implemented as self.grep(block).first.
say((1..100).lazy.first_by {|n| n.is_prime && n > 50 }) #=> 53
nth
self.nth(n)
Returns the n-th (1-indexed) resulting element, or nil if n is less than or equal to 0. This is a positional lookup on the pipeline's output, not a predicate search -- chain a "grep" first if you need the n-th element matching some condition.
say((^Inf).lazy.grep {|n| n.is_prime }.nth(5)) #=> 11
REDUCTION AND AGGREGATION
reduce
self.reduce(op_name)
self.reduce(op_name, initial)
self.reduce(block)
self.reduce(block, initial)
Drives the pipeline to completion, combining all resulting elements into a single value. If given a block, delegates entirely to "reduce_by". If given a string naming an operator (such as '+' or '*'), repeatedly calls that operator method on an accumulator, starting from initial (or the first element, if initial is omitted).
say((1..5).lazy.reduce('+')) #=> 15
say((1..5).lazy.reduce('*', 1)) #=> 120
reduce_by
self.reduce_by(block, initial=nil)
Drives the pipeline to completion, repeatedly calling block(accumulator, element) and using the result as the new accumulator. Starts from initial if given, or from the first element otherwise.
say((1..5).lazy.reduce_by {|acc, v| acc + v }) #=> 15
say((1..5).lazy.reduce_by({|acc, v| acc + v }, 10)) #=> 25
sum
self.sum
self.sum(block)
Drives the pipeline to completion and returns the sum of the resulting elements. With a block, sums block applied to each element instead. Processes elements in internal batches, so it doesn't need to hold the whole sequence in memory at once.
say((1..5).lazy.sum) #=> 15
say((1..5).lazy.sum {|n| n**2 }) #=> 55
Aliases: sum_by
prod
self.prod
self.prod(block)
Drives the pipeline to completion and returns the product of the resulting elements. With a block, multiplies block applied to each element instead. Processes elements in internal batches, so it doesn't need to hold the whole sequence in memory at once.
say((1..5).lazy.prod) #=> 120
say((1..5).lazy.prod {|n| n**2 }) #=> 14400
Aliases: prod_by
sum_kv
self.sum_kv(block)
Drives the pipeline to completion and returns the sum of calling block(index, element) for each resulting element, where index is a 0-based position in the resulting sequence (not the original, pre-pipeline one).
say((1..3).lazy.sum_kv {|k, v| k + v }) #=> 9 # (0+1) + (1+2) + (2+3)
prod_kv
self.prod_kv(block)
Drives the pipeline to completion and returns the product of calling block(index, element) for each resulting element, where index is a 0-based position in the resulting sequence (not the original, pre-pipeline one).
say((1..3).lazy.prod_kv {|k, v| k + v }) #=> 15 # (0+1) * (1+2) * (2+3)