Collections
Creating arrays
Section titled “Creating arrays”Use the #[...] literal syntax or the Array() constructor.
let empty = #[];let nums = #[1, 2, 3, 4, 5];let mixed = #[1, "hello", true]; // mixed types allowed
let also_nums = Array(1, 2, 3); // same as #[1, 2, 3]Accessing elements
Section titled “Accessing elements”Elements are zero-indexed. Use bracket notation or the .get() method.
nums[0]; // 1nums[2]; // 3nums.get(4); // 5Built-in functions
Section titled “Built-in functions”| Function | Description | Example |
|---|---|---|
length(arr) | Number of elements | length(#[1,2,3]) → 3 |
slice(arr, start, end) | Sub-array from start to end | slice(#[1,2,3,4], 1, 3) → [2,3] |
concat(a, b) | Join two arrays | concat(#[1,2], #[3,4]) → [1,2,3,4] |
range(n) | Array of 0..n-1 | range(5) → [0,1,2,3,4] |
range(start, end) | Array of start..end-1 | range(2, 5) → [2,3,4] |
Methods
Section titled “Methods”Methods are called with dot syntax on the array value.
Mutating methods
Section titled “Mutating methods”| Method | Description |
|---|---|
.push(item) | Append an item to the end |
.pop() | Remove and return the last item |
let chars = #["a", "b", "c"];chars.push("d"); // ["a", "b", "c", "d"]let last = chars.pop(); // "d", chars is now ["a", "b", "c"]Non-mutating methods
Section titled “Non-mutating methods”These return new arrays; the original is unchanged.
| Method | Description |
|---|---|
.get(index) | Element at index |
.length() | Number of elements |
.reverse() | Reversed copy |
.slice(start, end) | Sub-array |
.concat(other) | Concatenated copy |
.contains(value) | true if value is present |
let nums = #[1, 2, 3, 4, 5];
nums.reverse(); // [5, 4, 3, 2, 1]nums.slice(1, 4); // [2, 3, 4]nums.contains(3); // truenums.contains(99); // false
let a = #[1, 2];let b = #[3, 4];a.concat(b); // [1, 2, 3, 4]Nested arrays
Section titled “Nested arrays”let matrix = #[ #[1, 2, 3], #[4, 5, 6], #[7, 8, 9]];matrix[0][1]; // 2matrix[2][2]; // 9Higher-order methods
Section titled “Higher-order methods”Apply a function to every element and return a new array.
let doubled = #[1, 2, 3, 4, 5].map(fn(x) { return x * 2; });// [2, 4, 6, 8, 10]filter
Section titled “filter”Keep only elements for which the function returns true.
let evens = #[1, 2, 3, 4, 5].filter(fn(x) { return x % 2 == 0; });// [2, 4]reduce
Section titled “reduce”Accumulate a single value by applying a function to each element.
let sum = #[1, 2, 3, 4, 5].reduce(fn(acc, x) { return acc + x; }, 0);// 15Chaining
Section titled “Chaining”Higher-order methods can be chained:
let result = #[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(fn(x) { return x % 2 == 0; }) .map(fn(x) { return x * x; }) .reduce(fn(acc, x) { return acc + x; }, 0);// 220 (sum of squares of even numbers)Share-by-default
Section titled “Share-by-default”Assignment shares the same underlying array. Both variables see the same data.
let original = #[1, 2, 3];let alias = original;original.push(4);
PRINT original; // [1, 2, 3, 4]PRINT alias; // [1, 2, 3, 4]To create an independent copy, use .clone():
let original = #[1, 2, 3];let copy = original.clone();original.push(4);
PRINT original; // [1, 2, 3, 4]PRINT copy; // [1, 2, 3]Iteration
Section titled “Iteration”let colors = #["red", "green", "blue"];for color in colors { PRINT color;}Collect values during iteration:
let shout = #[];for color in colors { shout.push(color + "!");}PRINT shout; // ["red!", "green!", "blue!"]Iterators
Section titled “Iterators”Call .iter() on an array to create a lazy iterator. Iterators process elements on demand rather than all at once, and can be chained into pipelines. Use .collect() to materialize the result back into an array.
let nums = #[1, 2, 3, 4, 5];let it = nums.iter();it.next(); // 1it.next(); // 2Lazy methods
Section titled “Lazy methods”These return a new iterator without consuming elements. Nothing happens until you call an eager method.
| Method | Description |
|---|---|
.take(n) | Yield the first n elements |
.skip(n) | Skip the first n elements |
.step_by(n) | Yield every nth element |
.enumerate() | Yield [index, value] pairs |
.zip(iter) | Pair elements from two iterators |
.chain(iter) | Concatenate two iterators |
Eager methods
Section titled “Eager methods”These consume the iterator and return a value or array.
| Method | Returns | Description |
|---|---|---|
.collect() | Array | Gather all remaining elements into an array |
.count() | Number | Count remaining elements |
.first() | Value | First element (or NUL) |
.last() | Value | Last element (or NUL) |
.next() | Value | Next element (or NUL when exhausted) |
.find(fn) | Value | First element where fn returns true |
.any(fn) | Boolean | true if fn returns true for any element |
.all(fn) | Boolean | true if fn returns true for every element |
.map(fn) | Array | Apply fn to each element |
.filter(fn) | Array | Keep elements where fn returns true |
.fold(init, fn) | Value | Accumulate with fn(acc, elem) starting from init |
.flatten() | Array | Flatten one level of nested arrays |
take, skip, step_by
Section titled “take, skip, step_by”let nums = #[1, 2, 3, 4, 5];
nums.iter().take(3).collect(); // [1, 2, 3]nums.iter().skip(2).collect(); // [3, 4, 5]nums.iter().step_by(2).collect(); // [1, 3, 5]enumerate
Section titled “enumerate”Each element becomes an [index, value] pair.
let colors = #["red", "green", "blue"];let pairs = colors.iter().enumerate().collect();// [[0, "red"], [1, "green"], [2, "blue"]]Pair elements from two iterators. Stops when the shorter one is exhausted.
let names = #["kick", "snare", "hat"];let notes = #[36, 38, 42];
let pairs = names.iter().zip(notes.iter()).collect();// [["kick", 36], ["snare", 38], ["hat", 42]]Concatenate two iterators end-to-end.
let a = #[1, 2];let b = #[3, 4, 5];
a.iter().chain(b.iter()).collect(); // [1, 2, 3, 4, 5]find, any, all
Section titled “find, any, all”let nums = #[1, 2, 3, 4, 5];
nums.iter().find(fn(x) { return x > 3; }); // 4nums.iter().any(fn(x) { return x > 4; }); // truenums.iter().all(fn(x) { return x > 0; }); // trueflatten
Section titled “flatten”Flattens one level of nested structure. Non-array elements pass through unchanged.
let nested = #[#[1, 2], #[3], #[4, 5]];nested.iter().flatten(); // [1, 2, 3, 4, 5]Like reduce, but on iterators. Takes an initial value and an accumulator function.
let sum = #[1, 2, 3, 4, 5].iter().fold(0, fn(acc, x) { return acc + x; });// 15first, last
Section titled “first, last”Convenience accessors for the endpoints of an iterator.
let nums = #[10, 20, 30];nums.iter().first(); // 10nums.iter().last(); // 30Chaining lazy and eager methods
Section titled “Chaining lazy and eager methods”Lazy methods build a pipeline; an eager method at the end executes it.
let result = #[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .iter() .skip(2) .step_by(2) .take(3) .collect();// [3, 5, 7]Dictionaries map string or number keys to any value. They are created with the #{...} literal syntax.
Creating dictionaries
Section titled “Creating dictionaries”The #{} syntax uses a hash prefix to distinguish dictionaries from blocks. Keys and values are separated by colons, entries by commas.
let empty = #{};let person = #{"name": "Alice", "age": 30, "active": true};Trailing commas are allowed:
let config = #{ "volume": 80, "tempo": 120, "swing": 0.6,};Both keys and values are expressions evaluated at runtime:
let key = "tempo";let val = 60 * 2;let d = #{key: val};d["tempo"]; // 120Key types
Section titled “Key types”Keys must be strings or numbers. The two types are distinct — 200 and "200" are different keys.
let d = #{200: "OK", "200": "two hundred"};d[200]; // "OK"d["200"]; // "two hundred"Number keys are useful for MIDI mappings:
// MIDI note number to drum namelet drums = #{ 36: "kick", 38: "snare", 42: "closed hat", 46: "open hat",};
// CC number to parameter namelet cc_map = #{ 1: "mod wheel", 7: "volume", 10: "pan", 64: "sustain",};Accessing values
Section titled “Accessing values”Bracket notation
Section titled “Bracket notation”person["name"]; // "Alice"drums[36]; // "kick".get() (safe access)
Section titled “.get() (safe access)”.get() returns NUL for missing keys instead of raising an error.
person.get("name"); // "Alice"person.get("email"); // NULMutating
Section titled “Mutating”Index assignment
Section titled “Index assignment”let config = #{"volume": 80, "tempo": 120};config["volume"] = 100;.set(key, value)
Section titled “.set(key, value)”config.set("swing", 0.6);.remove(key)
Section titled “.remove(key)”Returns the removed value.
let old_tempo = config.remove("tempo");PRINT old_tempo; // 120Compound assignment
Section titled “Compound assignment”config["volume"] += 5;Inspection methods
Section titled “Inspection methods”| Method | Description |
|---|---|
.keys() | Array of all keys |
.values() | Array of all values |
.entries() | Array of [key, value] pairs |
.length() | Number of key-value pairs |
.contains_key(key) | true if key exists |
let d = #{"a": 1, "b": 2, "c": 3};
d.keys(); // ["a", "b", "c"]d.values(); // [1, 2, 3]d.entries(); // [["a", 1], ["b", 2], ["c", 3]]d.length(); // 3d.contains_key("a"); // trued.contains_key("z"); // false.merge(other) returns a new dictionary with entries from both. The original is unchanged. Keys in other overwrite keys in the receiver.
let defaults = #{"color": "blue", "size": 12, "bold": false};let overrides = #{"color": "red", "bold": true};let final_config = defaults.merge(overrides);
PRINT final_config; // {"color": "red", "size": 12, "bold": true}PRINT defaults; // {"color": "blue", "size": 12, "bold": false}Defaults/overrides pattern
Section titled “Defaults/overrides pattern”Merge is ideal for layering configuration. Define sensible defaults, then let the caller override only what they need:
let track_defaults = #{ "channel": 1, "velocity": 100, "octave": 4, "swing": 0.0,};
let lead = track_defaults.merge(#{"channel": 3, "octave": 5});let bass = track_defaults.merge(#{"channel": 2, "octave": 2, "velocity": 110});Chaining merges
Section titled “Chaining merges”Merges can be chained to layer multiple sources. Later values win:
let base = #{"a": 1, "b": 2, "c": 3};let layer1 = #{"b": 20};let layer2 = #{"c": 30, "d": 40};
let result = base.merge(layer1).merge(layer2);// {"a": 1, "b": 20, "c": 30, "d": 40}Higher-order methods
Section titled “Higher-order methods”.map(fn(k, v))
Section titled “.map(fn(k, v))”Apply a function to every entry and return a new dictionary with the same keys and transformed values.
let scores = #{"alice": 85, "bob": 92, "carol": 78};let doubled = scores.map(fn(k, v) { return v * 2; });// {"alice": 170, "bob": 184, "carol": 156}.filter(fn(k, v))
Section titled “.filter(fn(k, v))”Keep only entries for which the function returns true.
let top = scores.filter(fn(k, v) { return v > 90; });// {"bob": 92}.reduce(fn(acc, k, v), init)
Section titled “.reduce(fn(acc, k, v), init)”Accumulate a single value across all entries.
let total = scores.reduce(fn(acc, k, v) { return acc + v; }, 0);// 255Share-by-default
Section titled “Share-by-default”Assignment shares the same underlying dictionary. Both variables see the same data.
let original = #{"a": 1, "b": 2};let alias = original;original["c"] = 3;
PRINT original; // {"a": 1, "b": 2, "c": 3}PRINT alias; // {"a": 1, "b": 2, "c": 3}To create an independent copy, use .clone():
let original = #{"a": 1, "b": 2};let copy = original.clone();original["c"] = 3;
PRINT original; // {"a": 1, "b": 2, "c": 3}PRINT copy; // {"a": 1, "b": 2}Iteration
Section titled “Iteration”for-in on a dictionary yields keys. Use bracket notation to access the corresponding value.
let colors = #{"r": 255, "g": 128, "b": 0};for k in colors { PRINT f"{k} => {colors[k]}";}Iterating key-value pairs
Section titled “Iterating key-value pairs”Use .entries() to iterate over [key, value] pairs directly:
let cc_map = #{1: "mod wheel", 7: "volume", 10: "pan"};for entry in cc_map.entries() { let cc = entry[0]; let name = entry[1]; PRINT f"CC {cc}: {name}";}Building a new dict from iteration
Section titled “Building a new dict from iteration”let source = #{"a": 1, "b": 2, "c": 3};let doubled = #{};for k in source { doubled[k] = source[k] * 2;}// {"a": 2, "b": 4, "c": 6}Iterators
Section titled “Iterators”Call .iter() on a dictionary to create a lazy iterator over its keys. Iterators process elements on demand rather than all at once, and can be chained into pipelines. Use .collect() to materialize the result back into an array.
let d = #{"kick": 36, "snare": 38, "hat": 42};let it = d.iter();it.next(); // "kick"it.next(); // "snare"All standard iterator methods are available — take, skip, map, filter, collect, find, any, all, fold, and more. See the Arrays iterator section for the full method reference.
Chaining lazy methods
Section titled “Chaining lazy methods”let instruments = #{ "piano": 1, "bass": 2, "drums": 10, "strings": 3, "pad": 4,};
// Get the first 3 instrument nameslet first_three = instruments.iter().take(3).collect();Filtering keys
Section titled “Filtering keys”let cc_labels = #{1: "mod", 7: "vol", 10: "pan", 64: "sustain", 74: "cutoff"};
// Find CC numbers above 10let high_ccs = cc_labels.iter() .filter(fn(k) { return k > 10; }) .collect();// [64, 74]Nesting
Section titled “Nesting”Dictionaries can contain arrays and vice versa.
// Dict containing an arraylet playlist = #{ "name": "Focus", "tracks": #["Ambient 1", "Ambient 2", "Ambient 3"]};playlist["tracks"][0]; // "Ambient 1"
// Array of dictslet instruments = #[ #{"name": "Piano", "channel": 1}, #{"name": "Bass", "channel": 2}];instruments[1]["name"]; // "Bass"Nested dicts work well for instrument configurations:
let kit = #{ "kick": #{ "channel": 10, "note": 36, "velocity": #[100, 110, 120, 127], }, "snare": #{ "channel": 10, "note": 38, "velocity": #[80, 100, 110, 120], },};
kit["kick"]["note"]; // 36kit["snare"]["velocity"][2]; // 110