Iterators
Overview
Iterators provide lazy sequence generation in rill. They produce values on demand rather than materializing entire collections upfront.
Built-in iterators:
| Function | Description |
|---|---|
range(start, end, step?) | Generate number sequence |
repeat(value, count) | Repeat value n times |
.first() | Get iterator for any collection |
Key characteristics:
- Value-based:
.next()returns a new iterator, original unchanged - Lazy: Elements generated on demand
- Composable: Work with all collection operators (
each,map,filter,fold)
range(0, 5) -> each { $ * 2 } # [0, 2, 4, 6, 8]
repeat("x", 3) -> each { $ } # ["x", "x", "x"]
[1, 2, 3] -> .first() -> each { $ } # list[1, 2, 3]Iterator Protocol
Iterators are dicts with three fields:
| Field | Type | Description |
|---|---|---|
value | any | Current element (absent when done) |
done | bool | True if exhausted |
next | closure | Returns new iterator at next position |
# Iterator structure
[
value: 0,
done: false,
next: || { ... } # returns new iterator
]Collection operators automatically recognize and expand iterators:
range(1, 4) -> map { $ * 10 } # [10, 20, 30]
range(0, 10) -> filter { $ > 5 } # [6, 7, 8, 9]
range(1, 6) -> fold(0) { $@ + $ } # 15Built-in Iterators
range(start, end, step?)
Generate a sequence of numbers from start (inclusive) to end (exclusive).
| Parameter | Type | Default | Description |
|---|---|---|---|
start | number | required | First value |
end | number | required | Stop value (exclusive) |
step | number | 1 | Increment (can be negative) |
range(0, 5) # 0, 1, 2, 3, 4
range(1, 6) # 1, 2, 3, 4, 5
range(0, 10, 2) # 0, 2, 4, 6, 8
range(5, 0, -1) # 5, 4, 3, 2, 1
range(-3, 2) # -3, -2, -1, 0, 1
range(0, 1, 0.25) # 0, 0.25, 0.5, 0.75Edge cases:
range(5, 5) # empty (start == end)
range(5, 3) # empty (start > end with positive step)
range(0, 5, -1) # empty (wrong direction)
range(0, 5, 0) # ERROR: step cannot be zerorepeat(value, count)
Generate a value repeated n times.
| Parameter | Type | Description |
|---|---|---|
value | any | Value to repeat |
count | number | Number of repetitions |
repeat("x", 3) # "x", "x", "x"
repeat(0, 5) # 0, 0, 0, 0, 0
repeat([a: 1], 2) # dict[a: 1], dict[a: 1]
trueEdge cases:
repeat("x", 0) # empty
repeat("x", -1) # ERROR: count cannot be negativeThe .first() Method
Returns an iterator for any collection. Provides a consistent interface for manual iteration.
| Input Type | .first() Returns |
|---|---|
| list | Iterator over elements |
| string | Iterator over characters |
| dict | Iterator over [key: k, value: v] entries |
| iterator | Returns itself (identity) |
[1, 2, 3] -> .first() # iterator at 1
"abc" -> .first() # iterator at "a"
[a: 1, b: 2] -> .first() # iterator at dict[key: "a", value: 1]
range(0, 5) -> .first() # iterator at 0 (identity)
trueEmpty collections return a done iterator:
[] -> .first() # done iterator
"" -> .first() # done iterator
trueUsing .first() with collection operators:
[1, 2, 3] -> .first() -> each { $ * 2 } # list[2, 4, 6]
"hello" -> .first() -> each { $ } # ["h", "e", "l", "l", "o"]Manual Iteration
Traverse an iterator by accessing .value, .done, and calling .next():
[1, 2, 3] -> .first() => $it
# Check if done
$it.done # false
# Get current value
$it.value # 1
# Advance to next position
$it.next() => $it
$it.value # 2Loop pattern (using $ as accumulator):
"hello" -> .first() -> !$.done @ {
$.value -> log
$.next()
}
# logs: h, e, l, l, oPreferred: use each for iteration:
"hello" -> each { log($) }
# logs: h, e, l, l, oCheck before access:
[1, 2, 3] => $list
$list -> .first() => $it
$it.done ? "empty" ! $it.valueStreams vs Iterators
Streams and iterators share the same protocol fields but differ in source, statefulness, and invocation.
| Property | Iterator | Stream |
|---|---|---|
| Source | Synchronous | Asynchronous |
| Statefulness | Stateless, re-iterable | Stateful, single-use |
| Protocol | done, value, next | done, value, next (shared) |
| Stale access | N/A (immutable) | Halts with error |
| Operators | each, map, filter, fold | Same four operators |
| Invocation | Not callable | $s() returns resolution value |
| Production | Built-in functions | Host helper or :stream(T):R closure |
Iterators are value types: calling .next() returns a new iterator and the original is unchanged. Streams are stateful: each chunk consumed advances the stream permanently.
See Types for stream type signatures and chunk semantics. See Collections for how each, map, filter, and fold behave on streams.
Custom Iterators
Create custom iterators by implementing the protocol:
# Counter from start to max
|start, max| [
value: $start,
done: ($start > $max),
next: || { $counter($.value + 1, $max) }
] => $counter
$counter(1, 5) -> each { $ } # [1, 2, 3, 4, 5]Fibonacci sequence:
|a, b, max| [
value: $a,
done: ($a > $max),
next: || { $fib($.b, $.a + $.b, $max) }
] => $fib
$fib(0, 1, 50) -> each { $ } # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]Infinite iterator (use with limit):
|n| [
value: $n,
done: false,
next: || { $naturals($.value + 1) }
] => $naturals
# Take first 5 using fold with compound accumulator
$naturals(1) -> .first() -> fold([list: [], it: $]) {
($@.list -> .len >= 5) ? $@ -> break ! [
list: [...$@.list, $@.it.value],
it: $@.it.next()
]
} -> $.list # [1, 2, 3, 4, 5]Element Access: .head and .tail
For direct element access (not iteration), use .head and .tail:
| Method | Description |
|---|---|
.head | First element (errors on empty) |
.tail | Last element (errors on empty) |
[1, 2, 3] -> .head # 1
[1, 2, 3] -> .tail # 3
"hello" -> .head # "h"
"hello" -> .tail # "o"Empty collections error (no null in rill):
[] -> .head # ERROR: Cannot get head of empty list
"" -> .tail # ERROR: Cannot get tail of empty stringComparison with .first():
| Method | Returns | On Empty |
|---|---|---|
.head | Element directly | Error |
.first() | Iterator | Done iterator |
Examples
Sum of squares
range(1, 11) -> map { $ * $ } -> fold(0) { $@ + $ }
# 385 (1 + 4 + 9 + ... + 100)Generate index markers
range(0, 5) -> each { "Item {$}" }
# ["Item 0", "Item 1", "Item 2", "Item 3", "Item 4"]Retry pattern
repeat(1, 3) -> each {
attempt() => $result
($result.success == true) ? ($result -> break)
pause("00:00:01")
$result
}Filter even numbers
range(0, 20) -> filter { ($ % 2) == 0 }
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]Nested iteration
range(1, 4) -> each { $ => $row -> range(1, 4) -> each { $row * $ } }
# [list[1, 2, 3], list[2, 4, 6], list[3, 6, 9]]Limits
Iterators are expanded eagerly when passed to collection operators. A default limit of 10000 elements prevents infinite loops:
# This would error after 10000 elements
|n| [value: $n, done: false, next: || { $inf($.value + 1) }] => $inf
$inf(0) -> each { $ } # ERROR: Iterator exceeded 10000 elementsSee Also
- Collections —
each,map,filter,foldoperators - Closures — Closure semantics for custom iterators
- Reference — Complete language specification