Generators
Generators are functions that can pause their execution and produce a sequence of values lazily. They are defined with fn* and use the yield keyword to emit values one at a time.
Basic Generator #
fn* count_up(start: int, end: int) {
let mut i = start
while i <= end {
yield i
i = i + 1
}
}
for n in count_up(1, 5) {
print(n)
}
// 1
// 2
// 3
// 4
// 5
Infinite Generators #
Generators can be infinite — they produce values on demand:
fn* naturals() {
let mut n = 0
loop {
yield n
n = n + 1
}
}
fn* fibonacci() {
let mut a = 0
let mut b = 1
loop {
yield a
let next = a + b
a = b
b = next
}
}
Use Iter.take to consume a finite number of values from an infinite generator:
let first10 = fibonacci()
|> Iter.take(10)
|> Iter.collect()
print(first10)
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Generator with Parameters #
fn* range(start: int, end: int, step: int) {
let mut i = start
while i < end {
yield i
i = i + step
}
}
// Every 3rd number from 0 to 20
for n in range(0, 20, 3) {
print(n)
}
// 0, 3, 6, 9, 12, 15, 18
Using Generators with Iter #
Generators implement the Iter protocol, so all iterator combinators work:
fn* squares() {
let mut n = 0
loop {
yield n * n
n = n + 1
}
}
// First 5 even squares
let result = squares()
|> Iter.filter(|x| x % 2 == 0)
|> Iter.take(5)
|> Iter.collect()
print(result) // [0, 4, 16, 36, 64]
Early Return from Generator #
A generator can return early, stopping the sequence:
fn* bounded_fib(max: int) {
let mut a = 0
let mut b = 1
while a <= max {
yield a
let next = a + b
a = b
b = next
}
// generator ends here
}
for n in bounded_fib(100) {
print(n)
}
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
Yield with Expressions #
yield can return any expression:
fn* enumerate(items: [str]) {
let mut i = 0
for item in items {
yield (i, item)
i = i + 1
}
}
for (i, name) in enumerate(["Alice", "Bob", "Carol"]) {
print("{i}: {name}")
}
// 0: Alice
// 1: Bob
// 2: Carol
Generator vs Iter.from_fn
For simple cases you can also use Iter.from_fn with a closure that returns the next value:
// Generator style
fn* counter() {
let mut n = 0
loop { yield n; n = n + 1 }
}
// Iter.from_fn style
fn counter2() {
let mut n = 0
return Iter.from_fn(|| {
let val = n
n = n + 1
return val
})
}
Both produce identical lazy sequences. Generators are usually cleaner for complex stateful logic.
Collecting and Chaining #
fn* primes() {
let mut seen = []
let mut candidate = 2
loop {
let is_prime = seen
|> Array.all(|p| candidate % p != 0)
if is_prime {
yield candidate
seen = [...seen, candidate]
}
candidate = candidate + 1
}
}
let first_primes = primes()
|> Iter.take(10)
|> Iter.collect()
print(first_primes)
// [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]