Classes
This content is for v0.7. Switch to the latest version for up-to-date documentation.
Defining a class
Section titled “Defining a class”A class declares fields with let, a constructor with fn new(...), and methods with fn name(...).
class Counter { let count;
fn new(initial) { this.count = initial; }
fn get() { return this.count; }
fn increment() { return Counter(this.count + 1); }
fn add(n) { return Counter(this.count + n); }}Construct an instance by calling the class name as a function:
let c = Counter(0);PRINT c.get(); // 0Immutable by default
Section titled “Immutable by default”Methods return new instances rather than mutating in place. This makes method chaining safe and predictable.
let c1 = Counter(0);let c2 = c1.increment().increment().increment();
PRINT c2.get(); // 3PRINT c1.get(); // 0 (unchanged)Fields, constructor, and methods
Section titled “Fields, constructor, and methods”The Point example
Section titled “The Point example”class Point { let x; let y;
fn new(x_val, y_val) { this.x = x_val; this.y = y_val; }
fn add(other) { return Point(this.x + other.x, this.y + other.y); }
fn scale(factor) { return Point(this.x * factor, this.y * factor); }
fn distance_squared() { return this.x * this.x + this.y * this.y; }}
let p1 = Point(3, 4);PRINT p1.x; // 3PRINT p1.distance_squared(); // 25
let p2 = Point(1, 2);let p3 = p1.add(p2);PRINT p3.x; // 4PRINT p3.y; // 6External field assignment
Section titled “External field assignment”Fields can be set directly from outside the class:
let origin = Point(0, 0);origin.x = 42;PRINT origin.x; // 42Compound assignment
Section titled “Compound assignment”Compound operators work on fields:
let p = Point(10, 20);p.x += 5;PRINT p.x; // 15Type annotations
Section titled “Type annotations”Fields and method signatures can carry type annotations.
class Velocity { let speed: Number; let direction: String;
fn new(speed: Number, direction: String) { this.speed = speed; this.direction = direction; }
fn scale(factor: Number) -> Velocity { return Velocity(this.speed * factor, this.direction); }
fn __str__() -> String { return f"{this.speed} {this.direction}"; }}
let v = Velocity(120, "north");PRINT v.scale(0.5); // 60 northDoc comments
Section titled “Doc comments”Use /// to attach documentation to class methods. Doc comments are visible via LSP hover and the show() introspection function.
class Chord { let root; let intervals;
fn new(root, intervals) { this.root = root; this.intervals = intervals; }
/// Return the notes of this chord as an array. /// Each note is the root transposed by the corresponding interval. fn notes() { return this.intervals.map(fn(i) { return this.root + i; }); }
/// Invert the chord by shifting the lowest note up an octave. fn invert() { let new_intervals = this.intervals.slice(1, this.intervals.length()) .concat(#[this.intervals[0] + 12]); return Chord(this.root, new_intervals); }}Custom display with __str__
Section titled “Custom display with __str__”Define a __str__ method to control how instances appear in PRINT and format strings.
class Vector { let x; let y;
fn new(x_val, y_val) { this.x = x_val; this.y = y_val; }
fn __str__() { return f"({this.x}, {this.y})"; }
fn add(other) { return Vector(this.x + other.x, this.y + other.y); }}
let v1 = Vector(3, 4);let v2 = Vector(1, 2);PRINT v1; // (3, 4)PRINT f"v1 + v2 = {v1.add(v2)}"; // v1 + v2 = (4, 6)Default display
Section titled “Default display”Without __str__, printing an instance shows the class name and fields sorted alphabetically:
class Pair { let b; let a;
fn new(a, b) { this.a = a; this.b = b; }}
PRINT Pair(1, 2); // Pair { a: 1, b: 2 }__str__ must return a string
Section titled “__str__ must return a string”If __str__ returns a non-string value, the runtime falls back to the default display format.
Copy-on-assignment
Section titled “Copy-on-assignment”Assignment creates an independent deep copy. Mutating one instance does not affect the other.
let p1 = Point(10, 20);let p2 = p1;p1.x = 99;
PRINT p1.x; // 99PRINT p2.x; // 10To share state between variables, use References.
Mutable classes with references
Section titled “Mutable classes with references”By default, methods return new instances. To mutate in place, use References:
class Counter { let count;
fn new(initial) { this.count = initial; }
fn get() { return this.count; }
fn increment() { this.count += 1; // mutates in place }}
let counter = Counter(0);let counter_ref = &counter;
counter_ref.increment();PRINT counter.get(); // 1Type checking
Section titled “Type checking”Use the type() function to inspect the type of any value.
PRINT type(42); // "Number"PRINT type("hello"); // "String"PRINT type(true); // "Bool"PRINT type([C4 D4]); // "Pattern"PRINT type(Vector(1,2)); // "Vector"Practical examples
Section titled “Practical examples”Music abstractions
Section titled “Music abstractions”class Scale { let root; let intervals;
fn new(root, intervals) { this.root = root; this.intervals = intervals; }
/// Return the note at a scale degree (0-indexed). fn degree(n) { let octave = n / this.intervals.length(); let step = n % this.intervals.length(); return this.root + this.intervals[step] + octave * 12; }
fn __str__() { return f"Scale(root={this.root}, steps={this.intervals})"; }}
let c_minor = Scale(C4, #[0, 2, 3, 5, 7, 8, 10]);PRINT c_minor.degree(0); // C4PRINT c_minor.degree(4); // G4class DrumHit { let name; let note; let velocity;
fn new(name, note, velocity) { this.name = name; this.note = note; this.velocity = velocity; }
/// Return a louder version of this hit. fn accent() { return DrumHit(this.name, this.note, min(this.velocity + 20, 127)); }
/// Return a quieter ghost note. fn ghost() { return DrumHit(this.name, this.note, max(this.velocity - 40, 1)); }
fn __str__() { return f"{this.name}(v{this.velocity})"; }}
let kick = DrumHit("kick", 36, 100);PRINT kick.accent(); // kick(v120)PRINT kick.ghost(); // kick(v60)Instances in collections
Section titled “Instances in collections”Class instances work in arrays just like any other value:
let kit = #[ DrumHit("kick", 36, 100), DrumHit("snare", 38, 90), DrumHit("hat", 42, 70),];
for hit in kit { PRINT hit;}// kick(v100)// snare(v90)// hat(v70)class Stack { let items;
fn new(initial) { this.items = initial; }
fn push(item) { return Stack(this.items.concat(#[item])); }
fn pop() { return Stack(this.items.slice(0, this.items.length() - 1)); }
fn peek() { return this.items[this.items.length() - 1]; }
fn size() { return this.items.length(); }}
let stack = Stack(#[]);stack = stack.push(10).push(20).push(30);PRINT stack.size(); // 3PRINT stack.peek(); // 30
stack = stack.pop();PRINT stack.peek(); // 20Classes in modules
Section titled “Classes in modules”Classes can be exported from modules just like functions and variables. Importers instantiate them via module.ClassName(args).
class Circle { let radius;
fn new(r) { this.radius = r; }
fn area() { return 3.14159 * this.radius * this.radius; }}
private class InternalHelper { let data;}use "./shapes";
let c = shapes.Circle(5);PRINT c.area(); // 78.53975Mark a class private to keep it internal to the module — the same visibility rules as let and fn apply. See Modules for details.
Classes as patterns
Section titled “Classes as patterns”A class with a query(cycle) method can be used as a pattern. Assign the instance to a track and Resonon calls query() each cycle:
class Stepper { let notes; let idx;
fn new(notes) { this.notes = notes; this.idx = 0; }
fn query(cycle) { let note = this.notes[this.idx % this.notes.length]; this.idx = this.idx + 1; return note; }}
let p = Stepper(#[C4, D4, E4]);track(1, p);State mutations (this.idx = ...) persist across cycles. See Script Patterns for full details.