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, and returns the final accumulator. |
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.new | Returns zeroed iterator storage owned by the active Scope. |
Iter.product | Returns the product of the remaining elements, consuming iter. |
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
meta native 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:227
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:773
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:759
Iter.chain
meta native 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:528
Iter.count
meta native 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:804
Iter.enumerate
meta native 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:505
Iter.filter
meta native 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. A null func raises <bad-arg> on the
first invocation, after the source element has been consumed.
An empty source does not invoke or check func.
Raises: whatever the source, Func.apply, or func raises while pulling.
Source: lib/iter.x:347
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:790
Iter.foldl
Var Iter.foldl(Iter iter, Var seed, Func fn)
Folds fn over iter from seed, left to right, and returns the final
accumulator. Consumes the whole iterator. A void seed means “use the
first element as the seed”, so folding an empty iterator from void
returns void; any other seed is returned unchanged when there is
nothing to fold. A null fn raises <bad-arg> on the first combining
invocation. Inputs pulled before a failure stay consumed.
The accumulator and elements are passed as values. Empty input, or one
element with a void seed, does not invoke or check fn.
Raises: whatever the source, Func.apply, or fn raises.
Source: lib/iter.x:736
Iter.head
meta native 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:549
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:204
Iter.map
meta native 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 raises <bad-arg> on the first invocation,
after the source element has been consumed.
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 <void-op> when func returns void and the iterator
rejects it as an element.
Source: lib/iter.x:320
Iter.map2
meta native 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:491
Iter.max
meta native Var Iter.max(Iter iter)
Returns the largest remaining element, or void when there is none.
Consumes the iterator, comparing with Var.compare, 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:844
Iter.min
meta native Var Iter.min(Iter iter)
Returns the smallest remaining element, or void when there is none.
Consumes the iterator, comparing with Var.compare, 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:858
Iter.new
meta native Iter Iter.new(void)
Returns zeroed iterator storage owned by the active Scope.
Use this when an iterator itself must be returned or stored as a value.
Producers such as Map.keys initialize the result through their existing
destination argument. The iterator still borrows its source and callback,
which must outlive every pull. The active Scope must remain live for as
long as the returned handle is used.
Caller-supplied struct Iter storage remains preferable for a local
traversal because it allocates nothing.
Raises: <alloc-fail> when storage cannot be allocated.
Source: lib/iter.x:152
Iter.product
meta native 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:831
Iter.repeat
meta native 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:568
Iter.scan
meta native 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 <void-op> when fn returns void and the iterator
rejects it as an element. A null fn or dest returns NULL.
Source: lib/iter.x:395
Iter.sum
meta native 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:818
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:176
Iter.unique
meta native 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:591
Iter.zip
meta native 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:448
Iter.zip_with
meta native 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:465
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:132
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. Each column retains fewer consumed slots than
the larger of 256 and its unread backlog. Compaction leaves its capacity
unchanged. 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:647
Convenience 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
meta native 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:423
Iter.next
meta native 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:196
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:23
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:30
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:54
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:48
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).