Iterators
An iterator produces a sequence of values, one at a time, on demand. Every collection in Kestrel exposes one through iter(), and the iterator chain (map, filter, fold, etc.) is how you transform data without writing explicit loops.
for loops
The simplest way to consume a sequence:
let xs = [1, 2, 3];
for x in xs {
println("\(x)");
}
for works on anything that conforms to the Iterable protocol — arrays, dicts, sets, ranges — and on any Iterator directly, including custom types you write yourself.
Eager methods on arrays
Arrays come with eager map and filter(where:) that allocate and return a new Array right away:
let xs = [1, 2, 3, 4, 5];
let doubled = xs.map { it * 2 }; // [2, 4, 6, 8, 10]
let evens = xs.filter(where: { it % 2 == 0 }); // [2, 4]
These are the convenient choice for short arrays and one-step transformations. For multi-step chains, the lazy iterator chain below avoids the intermediate allocations.
The iterator chain
xs.iter() starts a lazy chain. Methods that transform an iterator return another iterator; methods that consume one return a value. The transforming adapters take labeled arguments — map(as:), filter(where:):
Transformations (lazy; return another iterator):
let xs = [1, 2, 3, 4, 5];
xs.iter().map(as: { it * 2 });
xs.iter().filter(where: { it > 0 });
xs.iter().take(3);
xs.iter().skip(2);
xs.iter().enumerate(); // yields (index, element) pairs
xs.iter().zip(["a", "b"].iter());
Terminal operations (consume the iterator; return a value):
let xs = [1, 2, 3, 4, 5];
xs.iter().collect(); // materializes into an Array
xs.iter().fold(from: 0, by: { (acc, x) in acc + x });
xs.iter().reduce(by: { (a, b) in a + b }); // Optional; .None when empty
xs.iter().count();
xs.iter().first(); // Optional[T]
xs.iter().first(where: { it > 3 }); // Optional[T]
xs.iter().any(where: { it < 0 });
xs.iter().all(where: { it > 0 });
xs.iter().contains(4);
xs.iter().sum(); // requires T: Addable
xs.iter().max(); // requires T: Comparable, returns Optional[T]
A typical chain — note that a chain stays on one line (a continuation line starting with . isn't valid syntax); bind intermediate steps with let if a chain gets long:
let scores = [12, 90, 37, 8];
let highTotal = scores.iter().filter(where: { it > 20 }).map(as: { it * 2 }).sum();
println("\(highTotal)"); // 254
Laziness
Iterator transformations are lazy — they don't do work until a terminal operation forces them. The chain above doesn't allocate a filtered array, then a mapped array, then sum — it pulls one element at a time, filters, maps, accumulates. For long sequences this is a big win. (The eager map/filter(where:) called directly on an array are the opposite: each one allocates its full result.)
Custom iterators
Define your own by conforming to Iterator:
struct Countdown {
var current: Int
}
extend Countdown: Iterator {
type Item = Int
public mutating func next() -> Optional[Int] {
if self.current < 0 {
.None
} else {
let value = self.current;
self.current = self.current - 1;
.Some(value)
}
}
}
for n in Countdown(current: 5) {
println("\(n)"); // 5, 4, 3, 2, 1, 0
}
next() returns Optional[Item] — None signals the sequence is exhausted. The rest of the iterator chain works automatically.