Iterators
An iterator is a lazy, pull-based view over a sequence. Instead of building intermediate arrays at
every step, you chain transformations into a pipeline and pull values through it one at a time —
which also means you can work with sequences that are huge, or even infinite, like a pattern.
Call .iter() on an array, string, dictionary, or pattern to get one.
let result = #[1, 2, 3, 4, 5, 6, 7, 8] .iter() .filter(fn(x) { return x % 2 == 0; }) // lazy .map(fn(x) { return x * 10; }) // lazy .collect(); // eager — runs the pipelinePRINT result; // [20, 40, 60, 80]How an iterator works
Section titled “How an iterator works”An iterator is single-use. .next() advances it one step and returns the current element; once
it is exhausted, .next() returns NUL forever:
let it = #[10, 20, 30].iter();PRINT it.next(); // 10PRINT it.next(); // 20PRINT it.next(); // 30PRINT it.next(); // NUL — exhaustedTo iterate the same data again, make a fresh iterator from the source:
let arr = #[1, 2, 3];let sum = arr.iter().fold(0, fn(acc, x) { return acc + x; });let doubled = arr.iter().map(fn(x) { return x * 2; }).collect(); // new iteratorPRINT sum; // 6PRINT doubled; // [2, 4, 6]Where iterators come from
Section titled “Where iterators come from”Arrays
Section titled “Arrays”let it = #[1, 2, 3, 4, 5].iter();PRINT it.collect();Strings
Section titled “Strings”String iterators yield individual characters:
PRINT "hello".iter().collect(); // [h, e, l, l, o]Dictionaries
Section titled “Dictionaries”Dictionary iterators yield keys (in unspecified order); index back in for values:
let d = #{"root": C4, "third": E4, "fifth": G4};for key in d.iter() { PRINT key + ": " + d[key];}Patterns
Section titled “Patterns”A pattern iterator yields events — and it is infinite, repeating forever. Always limit it
with .take(n) before any eager method:
let events = [C4 D4 E4].iter().take(6).collect();PRINT events.length(); // 6[C4 D4 E4].iter().collect(); // WRONG — runs foreverLazy vs eager
Section titled “Lazy vs eager”This is the distinction that makes iterators work. Lazy methods return a new iterator and do no work yet; eager methods consume elements and produce a result. Build a pipeline of lazy methods, then terminate it with one eager method.
| Method | Kind | 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 |
Transforming pipelines
Section titled “Transforming pipelines”take, skip, and step_by slice a sequence; chain them freely:
let nums = #[1, 2, 3, 4, 5];PRINT nums.iter().take(3).collect(); // [1, 2, 3]PRINT nums.iter().skip(2).collect(); // [3, 4, 5]PRINT nums.iter().step_by(2).collect(); // [1, 3, 5]
PRINT #[1, 2, 3, 4, 5, 6, 7, 8].iter() .skip(1) .step_by(2) .take(3) .collect(); // [2, 4, 6]map and filter chain in the same pipeline since both are lazy:
let result = #[1, 2, 3, 4, 5].iter() .filter(fn(x) { return x % 2 == 1; }) .map(fn(x) { return x * 10; }) .collect();PRINT result; // [10, 30, 50]enumerate, zip, chain
Section titled “enumerate, zip, chain”PRINT #["a", "b", "c"].iter().enumerate().collect();// [[0, a], [1, b], [2, c]]
let names = #["kick", "snare", "hat"];let notes = #[36, 38, 42];PRINT names.iter().zip(notes.iter()).collect();// [[kick, 36], [snare, 38], [hat, 42]]
PRINT #[1, 2].iter().chain(#[3, 4, 5].iter()).collect();// [1, 2, 3, 4, 5]zip stops as soon as either side is exhausted.
Reducing pipelines
Section titled “Reducing pipelines”Eager methods turn a pipeline into a single value:
let nums = #[1, 2, 3, 4, 5];PRINT nums.iter().find(fn(x) { return x > 3; }); // 4PRINT nums.iter().any(fn(x) { return x > 4; }); // truePRINT nums.iter().all(fn(x) { return x > 0; }); // truePRINT nums.iter().fold(0, fn(acc, x) { return acc + x; }); // 15fold is reduce for iterators: an initial value plus an accumulator function. flatten
collapses one level of nesting:
PRINT #[#[1, 2], #[3], #[4, 5]].iter().flatten(); // [1, 2, 3, 4, 5]Pattern-event iteration
Section titled “Pattern-event iteration”When you iterate a pattern, each element is an event with these accessors:
| Method | Returns | Description |
|---|---|---|
.note() | Number (0–127) | MIDI note number |
.velocity() | Number (0–127) | Velocity (getter) |
.velocity(v) | Event | New event with velocity v (0–127) |
.channel() | Number | MIDI channel |
.start() | Number | Start time in cycles |
.duration() | Number | Duration in cycles |
.end() | Number | start + duration |
.transpose(n) | Event | New event transposed by n semitones |
Extract note numbers from a melody — remember to .take() first:
let melody = [C4 D4 E4 F4];let notes = melody.iter() .take(4) .map(fn(e) { return e.note(); }) .collect();PRINT notes; // [60, 62, 64, 65]Iterators in for-loops
Section titled “Iterators in for-loops”for … in accepts iterators directly:
for pair in #["a", "b", "c"].iter().enumerate() { PRINT pair[0] + ": " + pair[1];}
for event in [C4 D4].iter().take(4) { // always limit pattern iterators PRINT event.note();}You can also loop straight over an array without .iter() — use the iterator form when you want
to take, skip, or otherwise shape the sequence first.
Pitfalls
Section titled “Pitfalls”- Single-use. A consumed iterator yields nothing more.
it.collect()twice returns the full array, then an empty one. Make a new iterator from the source for a second pass. - Infinite pattern iterators.
collect(),count(),last(), andfold()all consume the whole iterator — on a pattern that never ends, they hang. Put.take(n)first, every time.
// safe — limited before the eager callPRINT [C4 D4].iter().take(8).collect().length(); // 8Musical examples
Section titled “Musical examples”Analyse a melody
Section titled “Analyse a melody”let melody = [C4 E4 G4 B4 D5 C5];let notes = melody.iter().take(6).map(fn(e) { return e.note(); }).collect();
let highest = notes.iter().fold(0, fn(acc, n) { if n > acc { return n; } return acc; });let lowest = notes.iter().fold(127, fn(acc, n) { if n < acc { return n; } return acc; });PRINT "range: " + (highest - lowest) + " semitones"; // 14Build a pattern from a pipeline
Section titled “Build a pattern from a pipeline”Thin a scale down to every other note, then play the result as a Sequence:
let lead = MidiTrack(1);let scale = [C4 D4 E4 F4 G4 A4 B4 C5];
let thinned = scale.iter() .take(8) .step_by(2) .map(fn(e) { return e.note(); }) .collect();// [60, 64, 67, 72] — C4, E4, G4, C5
lead << Sequence(thinned);PLAY;Next Steps
Section titled “Next Steps”- Pattern Basics — where the events you iterate come from
- Transforming Patterns —
.map()and friends on patterns themselves - Script Patterns — class-based, stateful pattern generators