Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

lib/iter.x

Single-pass pull iterators.

Primary API

FunctionSummary
rangeReturns an inclusive integer range over caller-supplied iter storage.
Iter.allReports whether every remaining element satisfies pred.
Iter.anyReports whether any remaining element satisfies pred.
Iter.chainReturns a lazy iterator over first followed by second.
Iter.countReturns the number of remaining elements, consuming iter.
Iter.enumerateReturns a lazy iterator over (index value) Lists starting at start.
Iter.filterReturns a lazy iterator over elements accepted by func’s Var truthiness.
Iter.findReturns the first element accepted by pred’s Var truthiness, else void.
Iter.foldlFolds fn over iter from seed, left to right.
Iter.headReturns a lazy iterator over at most count leading source values.
Iter.iterReturns iter unchanged as its own iterator.
Iter.mapReturns a lazy iterator over func applied to each element of iter.
Iter.map2Returns a lazy iterator over fn(left, right), requiring fn.
Iter.maxReturns the largest remaining element, or void when there is none.
Iter.minReturns the smallest remaining element, or void when there is none.
Iter.productReturns the product of the remaining elements, consuming iter.
Iter.reduceFolds func over iter and returns the final accumulator.
Iter.repeatReturns a lazy iterator that yields value at most count times.
Iter.scanReturns a lazy iterator over every new accumulator of fn.
Iter.sumReturns the sum of the remaining elements, consuming iter.
Iter.try_nextAdvances iter, writing the next element through out.
Iter.uniqueReturns a lazy iterator that yields the first occurrence of each value.
Iter.zipReturns a lazy iterator over canonical (left right) Lists.
Iter.zip_withReturns a lazy iterator over fn(left, right), applied pairwise.

Functions

range

Iter range(int start, int end, int step, Iter iter)

Returns an inclusive integer range over caller-supplied iter storage. Both endpoints belong to the range when the step direction reaches them: range(1, 4, 1, &storage) yields 1, 2, 3, 4, and range(3, 1, -1, &storage) counts down 3, 2, 1. A step that would cross end stops short of it, so range(0, 9, 2, &storage) yields 0, 2, 4, 6, 8. A range pointed against its step is empty. A range needs no state struct and no cleanup.

Raises: <bad-arg> when step is zero. A null iter returns NULL without raising.

Source: lib/iter.x:289

Iter

Iter.all

int Iter.all(Iter iter, Func pred)

Reports whether every remaining element satisfies pred. Vacuously true for an empty iterator, decided before pred is consulted. Otherwise it stops at the first element the predicate rejects and answers 0, leaving the rest unconsumed. Elements are passed as values and the result uses ordinary Var truthiness.

Raises: whatever the source, Func.apply, or pred raises. A null pred answers 1 for an empty iterator and 0 for any other.

Source: lib/iter.x:780

Iter.any

int Iter.any(Iter iter, Func pred)

Reports whether any remaining element satisfies pred. Stops at the first element the predicate accepts, so the iterator is left positioned after it and the rest is never pulled. An empty iterator answers 0. Elements are passed as values and the result uses ordinary Var truthiness.

Raises: whatever the source, Func.apply, or pred raises. A null pred answers 0.

Source: lib/iter.x:764

Iter.chain

Iter Iter.chain(Iter first, Iter second, Iter dest)

Returns a lazy iterator over first followed by second. Pulls do not reach second until first is exhausted. Both sources and dest are borrowed and must outlive traversal. A null source contributes no elements, and a null dest returns NULL. Pulling may raise any cause raised by either source.

Source: lib/iter.x:471

Iter.count

int Iter.count(Iter iter)

Returns the number of remaining elements, consuming iter. Counting drains the iterator, and an Iter cannot be rewound, so if you need the elements as well, collect them with Iter.list or Iter.array and ask the collection for its length. The remaining element count must fit in int.

Raises: <void-op> for a source callback that yields void, plus any cause raised by that callback.

Source: lib/iter.x:815

Iter.enumerate

Iter Iter.enumerate(Iter iter, int start, Iter dest)

Returns a lazy iterator over (index value) Lists starting at start. The first pulled value is paired with exactly start, then the native int index increases by one for each source value. The source and dest are borrowed and must outlive traversal; start plus the number of successfully pulled values must remain within the int range.

Raises: <alloc-fail> or <size-limit> while interning a pair, plus any cause raised by the source. A null dest returns NULL.

Source: lib/iter.x:496

Iter.filter

Iter Iter.filter(Iter iter, Func func, Iter dest)

Returns a lazy iterator over elements accepted by func’s Var truthiness. The predicate fits in dest, so that storage is all you declare. Rejected elements are consumed without being yielded, so one request for an element can pull many from the source.

Elements are passed as values. Both iter and its storage, dest, and any dynamic or captured func must remain valid while the result is used.

A null dest returns NULL, and a null func yields an exhausted iterator. An empty source does not invoke or check func.

Raises: whatever the source, Func.apply, or func raises while pulling.

Source: lib/iter.x:339

Iter.find

Var Iter.find(Iter iter, Func pred)

Returns the first element accepted by pred’s Var truthiness, else void. Stops as soon as it finds one, so the iterator can be pulled further for the elements after the match. void means “no element matched”, which is unambiguous because no iterator may yield void. Elements are passed as values. Raises: whatever the source, Func.apply, or pred raises. A null pred returns void.

Source: lib/iter.x:799

Iter.foldl

Var Iter.foldl(Iter iter, Var seed, Func fn)

Folds fn over iter from seed, left to right. Iter.reduce(fn, seed) with the seed first and the combining function second. Every rule of Iter.reduce applies, including the void seed rule.

Raises: whatever the source, Func.apply, or fn raises.

Source: lib/iter.x:754

Iter.head

Iter Iter.head(Iter iter, int count, Iter dest)

Returns a lazy iterator over at most count leading source values. Construction consumes nothing. Pulling stops after count values or source exhaustion, whichever comes first, and leaves any later source values unconsumed. A nonpositive count yields nothing. The source and caller-owned dest must outlive traversal; a null dest returns NULL. Pulling may raise any cause raised by the source.

Source: lib/iter.x:535

Iter.iter

Iter Iter.iter(Iter x, Iter dest)

Returns iter unchanged as its own iterator. dest is ignored; ownership and remaining traversal state are unchanged.

Source: lib/iter.x:879

Iter.map

Iter Iter.map(Iter iter, Func func, Iter dest)

Returns a lazy iterator over func applied to each element of iter. dest is caller-owned struct Iter storage. It and the source iterator’s storage, plus any dynamic or captured func, must remain valid while the result is used. Elements are passed as values. Nothing runs until an element is pulled, and each pull travels back up the chain for exactly one element per stage, so taking two elements from a map over a range calls func twice. A null func passes elements through unchanged.

static Var _square(Var value) {
  return value * value;
}

int main(void) {
struct Iter source_storage, map_storage;
Iter squares = range(1, 4, 1, &source_storage).map(_square,
                                                  &map_storage);
printf("%s\n", squares.list().str());
  return 0;
}

A null dest returns NULL. An empty source does not invoke or check func. Raises: whatever the source, Func.apply, or func raises while pulling, including <bad-result> when func returns void.

Source: lib/iter.x:379

Iter.map2

Iter Iter.map2(Iter left, Iter right, Func fn, Iter dest)

Returns a lazy iterator over fn(left, right), requiring fn. The strict form of Iter.zip_with. Behavior is identical, except that a null fn returns NULL instead of yielding pairs. Use it when a missing callback should fail at construction instead of changing the element type.

The sources, destination, callback lifetime, value passing, and pull-time failures are those of Iter.zip_with. A null fn or dest returns NULL.

Source: lib/iter.x:451

Iter.max

Var Iter.max(Iter iter)

Returns the largest remaining element, or void when there is none. Consumes the iterator, comparing with Var ordering, which is total across types. Equal values keep the earliest, so the result is the first of any tie.

Raises: <void-op> for a source callback that yields void, plus any cause from the source or Var.compare.

Source: lib/iter.x:855

Iter.min

Var Iter.min(Iter iter)

Returns the smallest remaining element, or void when there is none. Consumes the iterator, comparing with Var ordering, which is total across types. Equal values keep the earliest, so the result is the first of any tie.

Raises: <void-op> for a source callback that yields void, plus any cause from the source or Var.compare.

Source: lib/iter.x:869

Iter.product

Var Iter.product(Iter iter)

Returns the product of the remaining elements, consuming iter. Starts from the integer 1 and multiplies with ordinary Var arithmetic, so an empty iterator produces 1 and numeric types promote as they would in an expression. Integer results wrap to Var.binary’s promoted type width.

Raises: any cause from the source or Var.binary while multiplying an element into the running product.

Source: lib/iter.x:842

Iter.reduce

Var Iter.reduce(Iter iter, Func func, Var initial)

Folds func over iter and returns the final accumulator. Consumes the whole iterator. A void initial means “use the first element as the seed”, so a reduce over an empty iterator returns void; any other initial is the seed and is returned unchanged when there is nothing to fold. A null func drains the iterator and returns the seed. The accumulator and elements are passed as values. Empty input, or one element with a void initial value, does not invoke or check func.

Raises: whatever the source, Func.apply, or func raises.

Source: lib/iter.x:729

Iter.repeat

Iter Iter.repeat(Var value, int count, Iter dest)

Returns a lazy iterator that yields value at most count times. A nonpositive count yields nothing. dest is caller-owned, and any storage referenced by value must outlive traversal. A null dest returns NULL. Pulling a repeated void raises <void-op>.

Source: lib/iter.x:514

Iter.scan

Iter Iter.scan(Iter iter, Var seed, Func fn, Iter dest)

Returns a lazy iterator over every new accumulator of fn. Each step computes fn(accumulator, element), keeps the result as the new accumulator, and yields it. seed is the initial accumulator and is never yielded, so a scan produces exactly as many elements as its source. fn is required. The accumulator and each element are passed as values.

static Var _add(Var acc, Var item) {
  return acc + item;
}

int main(void) {
struct Iter source_storage, scan_storage;
Iter running = range(1, 4, 1, &source_storage).scan(0, _add,
                                                   &scan_storage);
foreach (int total, running) printf("%d\n", total);
  return 0;
}

That prints 1, 3, 6, and 10; the seed 0 never appears.

The source and its storage, dest, and any dynamic or captured fn must remain valid while the result is used. Constructing the iterator does not invoke fn. Pulling may raise whatever Func.apply, the source, or fn raises, including <bad-result> when fn returns void. A null fn or dest returns NULL.

Source: lib/iter.x:605

Iter.sum

Var Iter.sum(Iter iter)

Returns the sum of the remaining elements, consuming iter. Starts from the integer 0 and adds with ordinary Var arithmetic, so numeric element types promote as they would in an expression and an empty iterator sums to 0. Use Iter.accumulate for the running totals.

Raises: any cause from the source or Var.binary while adding an element to the running total.

Source: lib/iter.x:829

Iter.try_next

int Iter.try_next(Iter iter, Var *out)

Advances iter, writing the next element through out. Returns nonzero when it produced an element and zero once the iterator is exhausted, and writes out only in the nonzero case. Prefer this form. Status travels separately from the payload, so no value is reserved to mean “finished”.

An Iter is single-pass. There is no rewind, so build a fresh iterator when you need a second traversal.

int main(void) {
struct Iter storage;
Iter counts = range(3, 1, -1, &storage);
Var value;
while (counts.try_next(&value)) printf("%d\n", value);
  return 0;
}

Raises: <void-op> when a source callback claims success with a void element, plus any cause raised by that callback. A null iter or out reads as exhausted without raising.

Source: lib/iter.x:201

Iter.unique

Iter Iter.unique(Iter iter, Iter dest)

Returns a lazy iterator that yields the first occurrence of each value. Equality and hashing follow Map, so source order decides which equal value survives. Construction allocates a Scope-owned seen table; pulls may grow it. The source, dest, and owning Scope must outlive traversal.

Raises: <alloc-fail>, <size-limit>, <invariant>, or a cause from the source, hashing, or equality while constructing or pulling. A null dest returns NULL without allocating.

Source: lib/iter.x:637

Iter.zip

Iter Iter.zip(Iter left, Iter right, Iter dest)

Returns a lazy iterator over canonical (left right) Lists. Destructure each pair with Var (a, b) = pair;. Pairing ends as soon as either source does. Each pull advances the left side first: if the right side is exhausted, that unmatched left value is consumed; if the left side is exhausted, the right side is not pulled. Both sources and dest must outlive traversal. Each yielded pair follows the lifetime of the List pool owning its canonical match.

Raises: <alloc-fail> or <size-limit> while interning a pair, plus any cause raised by either source. A null dest returns NULL.

Source: lib/iter.x:406

Iter.zip_with

Iter Iter.zip_with(Iter left, Iter right, Func fn, Iter dest)

Returns a lazy iterator over fn(left, right), applied pairwise. Like Iter.zip, but each pair is combined by fn instead of being built into a List, and the result likewise ends with the shorter side. fn is optional. A null fn yields two-element pair Lists as Iter.zip does. Iter.map2 is the same operation with the callback required.

Both sources and their storage, dest, and any dynamic or captured fn must remain valid while the result is used. Values are passed, not aliases into either source. A null dest returns NULL. If either source is empty, pulling does not invoke or check fn.

Raises: whatever either source, Func.apply, or fn raises while pulling. With a null fn, pair interning may raise <alloc-fail> or <size-limit>.

Source: lib/iter.x:435

Advanced and interop API

FunctionSummary
Iter.initInitializes caller-supplied iterator storage and returns it.
Iter.unzipReturns an iterator over the two column iterators of paired elements.

Iter

Iter.init

Self Iter.init(Self iter, Var obj, IterNextFn next, Var state)

Initializes caller-supplied iterator storage and returns it. iter is caller-owned storage, normally a struct Iter local. This call writes obj, the next callback, and the initial state, clears the auxiliary callback slot, and returns iter. It allocates nothing; the iterator lives as long as the supplied storage.

A callback fills *out and returns 1, or returns 0 to report exhaustion. It must never report success with void, which the element domain excludes. Iter.try_next raises <void-op> for that violation. A null next yields an already-exhausted iterator. Inside a callback, read the stored fields with ->; iter.next(...) is receiver syntax and calls the method instead of the field.

static int _evens_next(Iter iter, Var *out) {
  long value = iter.state.long();
  if (value > iter.obj.long()) return 0;
  *out = (int) value;
  iter.state = value + 2;
  return 1;
}

int main(void) {
struct Iter storage;
Iter evens = Iter.init(&storage, 6, _evens_next, 0);
Var value;
while (evens.try_next(&value)) printf("%d\n", value);
  return 0;
}

Storage must outlive every iterator derived from it, because a pipeline stage keeps a pointer to its source. Never return an Iter built over locals, since the iterator would outlive the struct it names; return a collected List or Array, or take the storage as a parameter. One struct Iter is one live iterator, so reusing a single storage variable for two stages of a pipeline silently overwrites the first.

A null iter returns NULL.

Source: lib/iter.x:136

Iter.unzip

Iter Iter.unzip(Iter iter, UnzipShared *shared, Iter dest)

Returns an iterator over the two column iterators of paired elements. Every element of iter must be a two-element List. The result yields two elements, the left column and then the right one, and is exhausted after that. Each column arrives as a Var holding an iterator embedded in shared. Passing nonnull storage to .iter(&storage) performs the Var conversion, but Iter.iter ignores that storage and returns the embedded column; shared remains its owner.

The columns are independent, and only the lag between them is buffered. Draining one column holds every element the other has not reached yet, so interleaving the two costs little and front-loading one costs memory proportional to the source. shared owns those buffers and must outlive both columns.

int main(void) {
List pairs = %((1 10) (2 20));
struct Iter source_storage, columns_storage, left_storage, right_storage;
UnzipShared shared;
Iter columns = pairs.iter(&source_storage).unzip(&shared,
                                                &columns_storage);
Iter lefts = columns.next().iter(&left_storage);
Iter rights = columns.next().iter(&right_storage);
printf("%s %s\n", lefts.list().str(), rights.list().str());
  return 0;
}

Raises: <bad-types> when an element is not a List, <bad-arg> when it is not a two-element List, or <alloc-fail> / <size-limit> while creating or growing the column buffers. A source may also raise while a column pulls. A null shared or dest returns NULL without raising or allocating.

Source: lib/iter.x:713

Compatibility API

FunctionSummary
Iter.accumulateReturns a lazy iterator over the running numeric sum of iter.
Iter.nextReturns the next element, or void once iter is exhausted.

Iter

Iter.accumulate

Iter Iter.accumulate(Iter iter, Var initial, Iter dest)

Returns a lazy iterator over the running numeric sum of iter. The numeric special case of Iter.scan. Each element is added through Var.binary with the same promotion and failure rules as Iter.sum, and the resulting Var is yielded without narrowing. A void initial starts the total at integer zero; any other initial seeds it, and the seed itself is never yielded. Prefer Iter.scan for a different operation and Iter.sum when only the final total matters.

Raises: any cause from the source or Var.binary while adding an element to the running total. A null dest returns NULL without raising.

Source: lib/iter.x:561

Iter.next

Var Iter.next(Iter iter)

Returns the next element, or void once iter is exhausted. An adapter over Iter.try_next, unambiguous because no iterator may yield void as an element. Prefer Iter.try_next in new code; it reports exhaustion separately from the element.

Raises: <void-op> when the source callback claims success with void, plus any cause raised by that callback.

Source: lib/iter.x:220

Public types

TypeKindSummary
IterstructCaller-owned handle to single-pass iterator state.
IterNextFncallbackAdvances an Iter, reporting success separately from the element.
UnzipColumnstructInternal column selector embedded in UnzipShared.
UnzipSharedstructCaller-owned buffering shared by the two iterators from Iter.unzip.

Iter

typedef struct Iter *Iter

Caller-owned handle to single-pass iterator state. A valid handle points to storage initialized by Iter.init or a collection adapter. Its storage, borrowed sources, and stored callbacks must outlive traversal. The handle has no destructor; auxiliary Map or Array storage follows the lifetime of its owning Scope.

Source: lib/iter.x:22

IterNextFn

typedef int (*IterNextFn)(Iter iter, Var *out)

Advances an Iter, reporting success separately from the element. The iterator borrows the callback and invokes it synchronously. A nonzero result must write one non-void value through out; zero reports exhaustion and must leave out unchanged.

Source: lib/iter.x:29

UnzipColumn

typedef struct { UnzipShared *shared, int column; } UnzipColumn

Internal column selector embedded in UnzipShared. Callers reserve it as part of that shared record and do not initialize or use it independently.

Source: lib/iter.x:53

UnzipShared

typedef struct UnzipShared UnzipShared

Caller-owned buffering shared by the two iterators from Iter.unzip. Its source and this record must outlive both columns. Buffer allocations belong to the Scope that owns the Arrays created at initialization; that Scope must remain live through all column pulls and provides cleanup.

Source: lib/iter.x:47

Design notes

Iter advances one element at a time and reports exhaustion separately. Lazy operations borrow their source iterators and callbacks. The caller supplies iterator storage, which must outlive traversal. Iterators cannot yield void.

Tests and examples

make verify (unittest/test-iter.x) and make examples (iter-examples).