Generators and Infinite Sequences
A generator is declared with fn*. Each call to the instance returns the next
yield; when the function ends, it returns nil. Because execution is suspended between
yields, generators can be infinite without consuming memory:
Finite counter, infinite Fibonacci with loop/yield, and a parameterized range with step — all consumed manually.
// Feature: Generators (`fn*` + `yield`)
// Syntax: `fn* name(args) { yield value }`. Each call to the instance
// returns the next `yield`; once finished, returns `nil`.
// When to use: infinite sequences, custom lazy values, sources that do
// not fit in memory, simple state machines.
// Finite counter.
fn* counter(limit: int) {
var i = 0
while i < limit {
yield i
i += 1
}
}
let gen = counter(3)
print(gen()) // 0
print(gen()) // 1
print(gen()) // 2
print(gen()) // nil
// Infinite generator — possible because it is lazy.
fn* fibonacci() {
var a = 0
var b = 1
loop {
yield a
let temp = a + b
a = b
b = temp
}
}
let fib = fibonacci()
for i in 0..7 {
print("fib({i}) = {fib()}")
}
// expected:
// fib(0) = 0
// fib(1) = 1
// fib(2) = 1
// fib(3) = 2
// fib(4) = 3
// fib(5) = 5
// fib(6) = 8
// Generator parameterized with a step.
fn* step_range(start: int, stop: int, step: int) {
var i = start
while i < stop {
yield i
i += step
}
}
let evens = step_range(0, 10, 2)
var v = evens()
while v != nil {
print(" even {v}")
v = evens()
}
Zolo also supports literal infinite ranges (0..) and additional operators
such as scan, windows, sum, and Iter::from_fn. The golden rule is to always
pair a take(n) before any terminal consumer:
Even squares, sum with .sum(), Fibonacci via Iter::from_fn, sliding windows, scan, zip of infinites, and Iter::repeat_val.
use std::Iter
// Lazy infinite iterators — examples
//
// Key rule: infinite ranges (0..) must always be paired with
// a terminator like take() before collect() or each().
// 1. First 5 squares of even numbers starting from 0
let squares = 0..
|> .map(|x| x * x)
|> .filter(|x| x % 2 == 0)
|> .take(5)
|> .collect()
print(squares)
// [0, 4, 16, 36, 64]
// 2. Sum of first 10 natural numbers (0..9)
let total = 0..
|> .take(10)
|> .sum()
print(total)
// 45
// 3. Fibonacci sequence via Iter.from_fn
fn fib_iter() {
var a = 0
var b = 1
return Iter::from_fn(|| {
let val = a
let next = a + b
a = b
b = next
return val
})
}
let fibs = fib_iter()
|> .take(10)
|> .collect()
print(fibs)
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
// 4. Sliding windows of size 3 over first 6 naturals
let windows = 0..
|> .take(6)
|> .windows(3)
|> .collect()
print(windows)
// [[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5]]
// 5. Scan (running sum)
let running = 1..
|> .take(5)
|> .scan(0, |acc, x| acc + x)
|> .collect()
print(running)
// [1, 3, 6, 10, 15]
// 6. Zip two infinite ranges
let pairs = Iter::zip(0.., 1..)
|> .take(4)
|> .collect()
print(pairs)
// [[0, 1], [1, 2], [2, 3], [3, 4]]
// 7. Infinite repeat
let ones = Iter::repeat_val(1)
|> .take(5)
|> .sum()
print(ones)
// 5
// 8. for loop with infinite range (use break to exit)
for i in 0.. {
if i >= 5 { break }
print(i)
}
// 0, 1, 2, 3, 4
Long pipelines that combine pipe, tap, and named functions translate complex transformations into readable step-by-step code:
String pipeline with tap; numeric data-first pipeline with map_arr/filter_arr/sum_arr; step-by-step inspection with variables.
// Feature: Long pipelines — full composition
// Syntax: combines `|>`, `&.`, and utility functions.
// When to use: small ETL, collection transformations, normalization,
// any case where the pipeline tells the story of the data flowing.
fn shout(s: str) -> str {
return s.to_upper()
}
fn add_bang(s: str) -> str {
return s + "!"
}
fn pad_dashes(s: str) -> str {
return "-- " + s + " --"
}
fn log_str(s: str) -> str {
print(" [step] {s}")
return s
}
// String pipeline with tap between steps — the classic shape.
let banner = "hello"
|> shout() &. log_str()
// HELLO
|> add_bang() &. log_str()
// HELLO!
|> pad_dashes()
// -- HELLO! --
print(banner)
// expected:
// [step] HELLO
// [step] HELLO!
// -- HELLO! --
// Long numeric pipeline: 1..10 -> *3 -> evens only -> sum.
fn triple(x: int) -> int {
return x * 3
}
fn is_even(x: int) -> bool {
return x % 2 == 0
}
fn map_arr(arr: [int], f: fn(int) -> int) -> [int] {
var out: [int] = []
for x in arr { out.push(f(x)) }
return out
}
fn filter_arr(arr: [int], pred: fn(int) -> bool) -> [int] {
var out: [int] = []
for x in arr {
if pred(x) { out.push(x) }
}
return out
}
fn sum_arr(arr: [int]) -> int {
var total = 0
for x in arr { total += x }
return total
}
// Data-first pipeline without tap (chaining tap on arrays in sequence
// has a runtime gotcha — break out the intermediate step into a
// variable when you want to inspect arrays).
let result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|> map_arr(triple)
|> filter_arr(is_even)
|> sum_arr()
print("total = {result}")
// expected: total = 90
// To inspect steps in an array pipeline, prefer variables and direct
// `print(arr)` calls (interpolating an array inside a string only
// shows the handle).
let tripled = map_arr([1, 2, 3, 4, 5], triple)
print(" [tripled]")
print(tripled)
let only_even = filter_arr(tripled, is_even)
print(" [even]")
print(only_even)
let total = sum_arr(only_even)
print(" [total] {total}")
// expected:
// [tripled]
// [3, 6, 9, 12, 15]
// [even]
// [6, 12]
// [total] 18
Challenge
Write a generator fn* powers(base: int) that produces increasing powers of
base (1, base, base², base³, …) and use it to print the first 5 powers of 2.
See also