Skip to content

Classes

This content is for v0.7. Switch to the latest version for up-to-date documentation.

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(); // 0

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(); // 3
PRINT c1.get(); // 0 (unchanged)
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; // 3
PRINT p1.distance_squared(); // 25
let p2 = Point(1, 2);
let p3 = p1.add(p2);
PRINT p3.x; // 4
PRINT p3.y; // 6

Fields can be set directly from outside the class:

let origin = Point(0, 0);
origin.x = 42;
PRINT origin.x; // 42

Compound operators work on fields:

let p = Point(10, 20);
p.x += 5;
PRINT p.x; // 15

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 north

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);
}
}

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)

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 }

If __str__ returns a non-string value, the runtime falls back to the default display format.

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; // 99
PRINT p2.x; // 10

To share state between variables, use 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(); // 1

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"
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); // C4
PRINT c_minor.degree(4); // G4
class 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)

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(); // 3
PRINT stack.peek(); // 30
stack = stack.pop();
PRINT stack.peek(); // 20

Classes can be exported from modules just like functions and variables. Importers instantiate them via module.ClassName(args).

shapes.non
class Circle {
let radius;
fn new(r) {
this.radius = r;
}
fn area() {
return 3.14159 * this.radius * this.radius;
}
}
private class InternalHelper {
let data;
}
main.non
use "./shapes";
let c = shapes.Circle(5);
PRINT c.area(); // 78.53975

Mark a class private to keep it internal to the module — the same visibility rules as let and fn apply. See Modules for details.

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.