lib/iter.x
Single-pass pull iterators.
Primary API
| Function | Summary |
|---|---|
range | Returns an inclusive integer range over caller-supplied iter storage. |
Iter.all | Reports whether every remaining element satisfies pred. |
Iter.any | Reports whether any remaining element satisfies pred. |
Iter.chain | Returns a lazy iterator over first followed by second. |
Iter.count | Returns the number of remaining elements, consuming iter. |
Iter.enumerate | Returns a lazy iterator over (index value) Lists starting at start. |
Iter.filter | Returns a lazy iterator over elements accepted by func’s Var truthiness. |
Iter.find | Returns the first element accepted by pred’s Var truthiness, else void. |
Iter.foldl | Folds fn over iter from seed, left to right. |
Iter.head | Returns a lazy iterator over at most count leading source values. |
Iter.iter | Returns iter unchanged as its own iterator. |
Iter.map | Returns a lazy iterator over func applied to each element of iter. |
Iter.map2 | Returns a lazy iterator over fn(left, right), requiring fn. |
Iter.max | Returns the largest remaining element, or void when there is none. |
Iter.min | Returns the smallest remaining element, or void when there is none. |
Iter.product | Returns the product of the remaining elements, consuming iter. |
Iter.reduce | Folds func over iter and returns the final accumulator. |
Iter.repeat | Returns a lazy iterator that yields value at most count times. |
Iter.scan | Returns a lazy iterator over every new accumulator of fn. |
Iter.sum | Returns the sum of the remaining elements, consuming iter. |
Iter.try_next | Advances iter, writing the next element through out. |
Iter.unique | Returns a lazy iterator that yields the first occurrence of each value. |
Iter.zip | Returns a lazy iterator over canonical (left right) Lists. |
Iter.zip_with | Returns 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
| Function | Summary |
|---|---|
Iter.init | Initializes caller-supplied iterator storage and returns it. |
Iter.unzip | Returns 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
| Function | Summary |
|---|---|
Iter.accumulate | Returns a lazy iterator over the running numeric sum of iter. |
Iter.next | Returns 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
| Type | Kind | Summary |
|---|---|---|
Iter | struct | Caller-owned handle to single-pass iterator state. |
IterNextFn | callback | Advances an Iter, reporting success separately from the element. |
UnzipColumn | struct | Internal column selector embedded in UnzipShared. |
UnzipShared | struct | Caller-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).