Iterators
Iterators provide lazy, pull-based access to sequences. They let you process elements one at a time without loading everything into memory.
What is an Iterator?
Section titled “What is an Iterator?”An iterator is a single-use sequence. Once an element is consumed, it cannot be retrieved again. This makes iterators efficient for processing large or infinite sequences.
Calling .next() advances the iterator by one position and returns the current element. When the iterator is exhausted, .next() returns nul:
| Call | Position | Returns | Remaining |
|---|---|---|---|
| — | start | — | 10, 20, 30 |
.next() | 1 | 10 | 20, 30 |
.next() | 2 | 20 | 30 |
.next() | 3 | 30 | (empty) |
.next() | — | nul | (exhausted) |
let it = #[10, 20, 30].iter();it.next() // 10it.next() // 20it.next() // 30it.next() // nulCreating Iterators
Section titled “Creating Iterators”From Arrays
Section titled “From Arrays”let it = #[1, 2, 3, 4, 5].iter();Array iterators are finite — they end when the array is exhausted.
From Strings
Section titled “From Strings”let chars = "hello".iter();String iterators yield individual characters.
From Dicts
Section titled “From Dicts”let d = %{ "a": 1, "b": 2, "c": 3 };let keys = d.iter();Dict iterators yield keys only. To access values, index back into the dict:
let d = %{ "root": C4, "third": E4, "fifth": G4 };
for key in d.iter() { PRINT key + ": " + d[key];}From Patterns
Section titled “From Patterns”let events = [C4 D4 E4].iter();// CORRECT: limit with take()let events = [C4 D4 E4].iter().take(6).collect();
// WRONG: will run forever!// let events = [C4 D4 E4].iter().collect();Lazy vs Eager Semantics
Section titled “Lazy vs Eager Semantics”Iterator methods fall into two categories: lazy methods return a new iterator (no work happens yet), and eager methods consume elements immediately and return a result.
| Method | Behavior | Returns |
|---|---|---|
take(n) | Lazy | Iterator |
skip(n) | Lazy | Iterator |
step_by(n) | Lazy | Iterator |
enumerate() | Lazy | Iterator (of [index, value]) |
zip(other) | Lazy | Iterator (of [a, b]) |
chain(other) | Lazy | Iterator |
map(fn) | Lazy | Iterator |
filter(fn) | Lazy | Iterator |
next() | Eager | Value or nul |
first() | Eager | Value or nul |
last() | Eager | Value or nul |
count() | Eager | Number |
collect() | Eager | Array |
find(fn) | Eager | Value or nul |
any(fn) | Eager | Boolean |
all(fn) | Eager | Boolean |
fold(init, fn) | Eager | Value |
flatten() | Eager | Array |
The pipeline model: chain lazy operations to set up the transformation, then terminate with an eager operation to produce a result.
#[1, 2, 3, 4, 5, 6, 7, 8].iter() .skip(2) // lazy — skip first 2 .take(4) // lazy — limit to 4 .collect() // eager — produce Array(3, 4, 5, 6)Basic Iterator Methods
Section titled “Basic Iterator Methods”Consuming Methods
Section titled “Consuming Methods”These methods consume elements and return single values:
let it = #[1, 2, 3, 4, 5].iter();
it.next() // Returns next element or nul if exhaustedit.first() // Returns first element (same as next())it.last() // Returns last element (WARNING: hangs on infinite iterators!)it.count() // Count elements (consumes iterator)Transforming Methods
Section titled “Transforming Methods”These methods return new iterators:
let it = #[1, 2, 3, 4, 5].iter();
it.take(3) // First 3 elementsit.skip(2) // Skip first 2 elementsit.step_by(2) // Every 2nd elementit.enumerate() // Yields [index, value] pairsTransforming methods chain naturally:
let result = #[1, 2, 3, 4, 5, 6, 7, 8].iter() .skip(1) .step_by(2) .take(3) .collect();// Array(2, 4, 6)Collecting
Section titled “Collecting”Convert an iterator back to an array:
let arr = #[1, 2, 3].iter().take(2).collect();// Array(1, 2)Higher-Order Methods
Section titled “Higher-Order Methods”These methods take functions as arguments.
Note: map and filter are lazy — they return iterators. Append .collect() to get an array.
let it = #[1, 2, 3, 4, 5].iter();
// Transform each element (lazy — returns iterator, collect to get array)it.map(fn(x) { return x * 2; }).collect() // Array(2, 4, 6, 8, 10)
// Keep matching elements (lazy — returns iterator, collect to get array)it.filter(fn(x) { return x > 2; }).collect() // Array(3, 4, 5)
// Find first match (returns value or nul)it.find(fn(x) { return x > 3; }) // 4
// Check if any/all elements matchit.any(fn(x) { return x > 4; }) // trueit.all(fn(x) { return x > 0; }) // true
// Reduce to single valueit.fold(0, fn(acc, x) { return acc + x; }) // 15Chaining map and filter
Section titled “Chaining map and filter”Since map() and filter() are lazy, they chain directly — no intermediate .collect() or .iter() needed:
// Filter then map in one pipelinelet result = #[1, 2, 3, 4, 5].iter() .filter(fn(x) { return x % 2 == 1; }) // lazy — Iterator .map(fn(x) { return x * 10; }) // lazy — Iterator .collect(); // Array(10, 30, 50)Combining Iterators
Section titled “Combining Iterators”Combine two iterators into pairs. Stops when either iterator is exhausted:
let a = #[1, 2, 3].iter();let b = #[4, 5, 6].iter();
let zipped = a.zip(b).collect();// [[1, 4], [2, 5], [3, 6]]Concatenate iterators end-to-end:
let a = #[1, 2].iter();let b = #[3, 4].iter();
let chained = a.chain(b).collect();// Array(1, 2, 3, 4)Flatten
Section titled “Flatten”Flatten nested arrays (eager, returns array):
let nested = #[#[1, 2], #[3, 4]].iter();let flat = nested.flatten();// Array(1, 2, 3, 4)Iterator Composition Patterns
Section titled “Iterator Composition Patterns”Common patterns for combining iterator methods:
| Pattern | Methods | Effect | Use case |
|---|---|---|---|
| Extract-transform | take + map | Pull N elements and transform | Grabbing a fixed slice of data |
| Windowing | skip + take | Select a range from the middle | Accessing a subrange |
| Sampling | step_by | Every Nth element | Thinning a sequence |
| Pairing | zip | Parallel iteration over two sources | Combining notes + velocities |
| Accumulation | fold | Reduce to a single value | Computing statistics |
| Search | find / any / all | Query the sequence | Looking for specific elements |
| Extend | chain | Concatenate multiple sources | Building longer sequences |
Worked Example: Pipeline
Section titled “Worked Example: Pipeline”Step-by-step breakdown of a chained pipeline:
let result = #[10, 20, 30, 40, 50, 60, 70, 80].iter() .skip(2) // skip 10, 20 → [30, 40, 50, 60, 70, 80] .take(4) // take 4 → [30, 40, 50, 60] .collect(); // Array(30, 40, 50, 60)| Step | Operation | Remaining sequence |
|---|---|---|
| Start | .iter() | 10, 20, 30, 40, 50, 60, 70, 80 |
| 1 | .skip(2) | 30, 40, 50, 60, 70, 80 |
| 2 | .take(4) | 30, 40, 50, 60 |
| 3 | .collect() | Array(30, 40, 50, 60) |
Pattern Iteration
Section titled “Pattern Iteration”Pattern iterators yield events that represent individual notes or samples in the pattern.
Event Methods
Section titled “Event Methods”Events from pattern iteration support these methods:
| Method | Returns | Description |
|---|---|---|
.note() | Number (0–127) | MIDI note number |
.velocity() | Number (0–127) | Note velocity (getter) |
.velocity(v) | Event | New event with velocity set to v (setter, clamped 0–127) |
.channel() | Number | MIDI channel |
.start() | Number | Start time in cycles |
.duration() | Number | Duration in cycles |
.end() | Number | End time (start + duration) |
.transpose(n) | Event | New event transposed by n semitones |
Iterating Over Patterns
Section titled “Iterating Over Patterns”// Always use take() for patterns!for event in [C4 D4 E4].iter().take(6) { PRINT event.note();}Without .take(), this loop would run forever.
Extracting Notes
Section titled “Extracting Notes”let melody = [C4 D4 E4 F4];
// Get all note numberslet notes = melody.iter() .take(4) .map(fn(e) { return e.note(); }) .collect();// Array(60, 62, 64, 65)Pattern Analysis Pipeline
Section titled “Pattern Analysis Pipeline”let pattern = [C4 E4 G4 B4];
// Extract events and compute the average MIDI notelet events = pattern.iter().take(4);let avg = events.fold( #[0, 0], fn(acc, e) { return #[acc[0] + e.note(), acc[1] + 1]; });let average_note = avg[0] / avg[1];PRINT "Average MIDI note: " + average_note;// Average MIDI note: 65.25For-In Loops
Section titled “For-In Loops”Iterators work with for-in loops:
// Array iterationfor x in #[1, 2, 3].iter() { PRINT x;}
// String iterationfor char in "hello".iter() { PRINT char;}
// Pattern iteration (with limit!)for event in [C4 D4].iter().take(4) { PRINT event.note();}
// Enumerate gives index and valuefor pair in #["a", "b", "c"].iter().enumerate() { PRINT pair[0] + ": " + pair[1];}You can also iterate directly over arrays without .iter():
for x in #[1, 2, 3] { PRINT x;}Debugging & Pitfalls
Section titled “Debugging & Pitfalls”Iterator Exhaustion
Section titled “Iterator Exhaustion”Iterators are single-use. After all elements are consumed, every call to .next() returns nul:
let it = #[1, 2].iter();it.next() // 1it.next() // 2it.next() // nul — exhaustedit.next() // nul — still exhaustedTo iterate again, create a new iterator from the source:
let arr = #[1, 2, 3];
// First passlet sum = arr.iter().fold(0, fn(acc, x) { return acc + x; });
// Second pass — new iterator from the same arraylet doubled = arr.iter().map(fn(x) { return x * 2; }).collect();Forgetting .take() on Pattern Iterators
Section titled “Forgetting .take() on Pattern Iterators”Pattern iterators never end. Any eager method that consumes the entire iterator will hang:
// These will all hang on pattern iterators:// [C4 D4].iter().collect()// [C4 D4].iter().count()// [C4 D4].iter().last()// [C4 D4].iter().fold(...)
// Always limit first:[C4 D4].iter().take(8).collect() // safeUsing a Consumed Iterator
Section titled “Using a Consumed Iterator”Passing a consumed iterator to a method returns an empty result:
let it = #[1, 2, 3].iter();let first_pass = it.collect(); // Array(1, 2, 3)let second_pass = it.collect(); // Array() — empty, already consumedIterators vs Other Approaches
Section titled “Iterators vs Other Approaches”.iter() pipeline | for loop on array | Pattern .map() | |
|---|---|---|---|
| Works on | Arrays, strings, dicts, patterns | Arrays, iterators | Patterns |
| Lazy chaining | Yes (take, skip, step_by, etc.) | No | No |
| Returns | Array or value (after eager op) | Nothing (side effects only) | Pattern |
| Best for | Data extraction, analysis, transformation pipelines | Side effects (PRINT, assignments) | Transforming pattern output for playback |
| Infinite-safe | Yes, with .take() | Yes, with .take() | Yes (patterns handle cycles internally) |
Musical Examples
Section titled “Musical Examples”Pattern Analysis
Section titled “Pattern Analysis”Extract note data from a pattern and compute statistics:
let melody = [C4 E4 G4 B4 D5 C5];
// Get note numberslet notes = melody.iter() .take(6) .map(fn(e) { return e.note(); }) .collect();// Array(60, 64, 67, 71, 74, 72)
// Find the highest notelet highest = notes.iter() .fold(0, fn(acc, n) { if n > acc { return n; } return acc; });PRINT "Highest: " + highest; // 74 (D5)
// Find the lowest notelet lowest = notes.iter() .fold(127, fn(acc, n) { if n < acc { return n; } return acc; });PRINT "Lowest: " + lowest; // 60 (C4)PRINT "Range: " + (highest - lowest) + " semitones"; // 14Building Patterns from Iterator Pipelines
Section titled “Building Patterns from Iterator Pipelines”Zip notes and velocities together, then use the data to construct a new pattern:
let track = MidiTrack(1);
let notes = #[C4, E4, G4, B4];let velocities = #[100, 80, 90, 70];
// Pair notes with velocitieslet pairs = notes.iter() .zip(velocities.iter()) .collect();// [[C4, 100], [E4, 80], [G4, 90], [B4, 70]]
// Use the pairs to build a velocity-shaped arpeggioclass VelocityArp { let pairs; fn new(pairs) { this.pairs = pairs; } fn query(cycle) { let pair = this.pairs[cycle % 4]; return pair[0].velocity(pair[1]); }}
track << VelocityArp(pairs);PLAY;Sampling Pattern Events
Section titled “Sampling Pattern Events”Use step_by to thin a pattern, keeping every other note:
let track = MidiTrack(1);
let scale = [C4 D4 E4 F4 G4 A4 B4 C5];
// Take every other note from the scalelet thinned = scale.iter() .take(8) .step_by(2) .map(fn(e) { return e.note(); }) .collect();// Array(60, 64, 67, 72) — C4, E4, G4, C5
PRINT thinned;See Also
Section titled “See Also”- Pattern Basics — Creating patterns
- Pattern Methods — Transforming patterns with
.map(),.filter(), and more - Script Patterns — Class-based stateful patterns
- Streams — Stateful patterns that carry values between cycles
- Markov Chains — Probabilistic state transitions