Skip to content

Variables & Types

Resonon supports three styles of comment.

// Single-line comment
/* Multi-line
comment */
/// Doc comment — attached to the next function definition.
/// Displayed by show().
fn double(x) {
return x * 2;
}

Integer, decimal, scientific, hexadecimal, and binary notation are all valid number literals. Hex and binary literals support underscore separators for readability.

42 // integer
3.14 // decimal
1e-3 // scientific notation (0.001)
2.5e2 // scientific notation (250)
.5 // leading dot (0.5)
5. // trailing dot (5.0)
0xFF // hexadecimal (255)
0b1010 // binary (10)
0xFF_FF // hex with separator (65535)
0b1010_0011 // binary with separator (163)

Strings are delimited by double quotes. See Strings for methods and format strings.

"hello world"
"Hello, " + "RESONON!" // concatenation with +
true
false

Only false and NUL are falsy — everything else is truthy, including values that are falsy in many other languages:

// .filter() keeps elements whose callback returns a truthy value.
// 0, "", and #[] are all truthy — only false and NUL are falsy.
#[0, "", false, NUL, #[]].filter(fn(x) { return x; });
// → [0, "", []]

Truthiness is used by higher-order methods like .filter(), .find(), .any(), and .all(), which test the return value of their callback for truthiness.

NUL represents the absence of a value. Undefined variables evaluate to NUL.

PRINT NUL; // NUL
NUL == NUL; // true
5 == NUL; // false
NUL == false; // false
NUL == 0; // false
// Check whether a variable is defined
if x != NUL {
PRINT "x is defined";
}

Missing function arguments default to NUL, and functions without an explicit return yield NUL.

fn greet(name) {
if name != NUL {
PRINT "Hello, " + name;
}
}
greet(); // name is NUL — prints nothing
fn no_return() { let x = 1; }
PRINT no_return(); // NUL
// NUL does NOT propagate through operations (unlike SQL NULL)
NUL + 1; // type error
NUL * 2; // type error
// But NUL can be stored in collections normally
let items = #[1, NUL, 3];
let config = #{ "key": NUL };

Notes follow the pattern Name Accidental? Octave where the octave ranges from -1 to 9.

C4 // middle C
A4 // concert A (440 Hz by default)
Bb3 // B-flat in octave 3
D#5 // D-sharp in octave 5
C-1 // lowest MIDI note
G9 // highest MIDI note

Notes map to MIDI values 0–127 (C-1 through G9). Notes outside this range produce a parse error. In patterns, transposition can temporarily exceed 0–127; clamping happens at MIDI output.

Notes compare with Numbers by MIDI value:

C4 == 60; // true
D4 > C4; // true
A4 >= 69; // true

Direct note arithmetic (C4 + 2) is a type error — transposition works through pattern combinators.

An underscore _ represents a rest (silence) inside a pattern.

[C4 _ E4 _ G4] // notes with rests between them

Outside a pattern, _ creates a standalone Rest value that can be stored and passed around.

let r = _;
PRINT r; // _
let items = #[C4, _, E4]; // rests can live in arrays and dicts
PRINT items[1]; // _
// Rest is truthy (e.g. kept by .filter())
#[C4, _, E4].filter(fn(x) { return x; }); // [C4, _, E4]
// Arithmetic with rests is a type error
_ + 1; // error: cannot add Rest and Number

Declare variables with let. Resonon is dynamically typed, so any variable can hold any value.

let x = 10;
let name = "RESONON";
let melody = [C4 D4 E4];

Variables can be reassigned to a different type at any time.

let val = 42;
val = "now a string";
val = [C4 E4 G4];
let count = 1;
count += 1; // 2

Variables declared with let inside a block are local to that block. Inner blocks can shadow outer variables.

let x = "outer";
{
let x = "inner"; // shadows outer x
PRINT x; // "inner"
}
PRINT x; // "outer"

Assignment always creates an independent deep copy. To share state, use References.

let a = #[1, 2, 3];
let b = a; // b is a separate copy
b.push(99);
PRINT a; // [1, 2, 3] — unchanged
PRINT b; // [1, 2, 3, 99]

Resonon is strict — most operations require matching types and there is no implicit coercion. A few key cross-type rules exist:

  • + also concatenates strings and transposes events
  • == / != work cross-type for Note↔Number and NUL↔anything
  • < / > / <= / >= work for Note↔Number comparisons
  • && / || require Boolean operands
  • Mismatched types in other operations produce a type error
C4 == 60; // true — Note↔Number comparison by MIDI value
"hi" + 1; // type error — no implicit number-to-string
true && 0; // type error — && requires Booleans

See Operators for the full operator reference.

Resonon has two built-in collection types:

  • Arrays — ordered collections created with #[...] or Array(...). See Arrays.
  • Dictionaries — key-value maps created with #{...}. See Dictionaries.

By default, assignment in Resonon creates an independent deep copy. References provide an opt-in mechanism for shared mutable state when you need multiple variables to see the same data.

Use the & operator on an array, dictionary, or class instance. Only arrays, dicts, and class instances can be referenced — not primitives.

let nums = #[1, 2, 3];
let nums_ref = &nums;

Dictionaries can be referenced just like arrays. Mutations through the reference are visible to all variables sharing the same dict.

let config = #{channel: 1, velocity: 100, transpose: 0};
let config_ref = &config;
config_ref.set("velocity", 80);
PRINT config["velocity"]; // 80
config_ref.set("transpose", 12);
PRINT config["transpose"]; // 12

Changes made through a reference are visible to all variables that share the same underlying value.

let nums = #[1, 2, 3];
let nums_ref = &nums;
nums_ref.push(4);
PRINT nums; // [1, 2, 3, 4]
nums_ref.pop();
PRINT nums; // [1, 2, 3]
class Counter {
let count;
fn new(initial) {
this.count = initial;
}
fn get() {
return this.count;
}
fn increment() {
this.count += 1;
}
fn add(n) {
this.count += n;
}
}
let counter = Counter(0);
let counter_ref = &counter;
counter_ref.increment();
PRINT counter.get(); // 1
counter_ref.add(10);
PRINT counter.get(); // 11

Declare a reference parameter with & in the function signature. Pass a reference as the argument.

fn double_counter(&c) {
c.add(c.get());
}
let my_counter = Counter(5);
let ref_counter = &my_counter;
double_counter(ref_counter);
PRINT my_counter.get(); // 10

Multiple references to the same value all see the same mutations.

let shared = #[1, 2, 3];
let ref1 = &shared;
let ref2 = &shared;
ref1.push(100);
ref2.push(200);
PRINT shared; // [1, 2, 3, 100, 200]

Fields and methods work through references automatically (auto-deref). No special syntax is needed.

class Point {
let x;
let y;
fn new(x_val, y_val) {
this.x = x_val;
this.y = y_val;
}
fn move_by(dx, dy) {
this.x += dx;
this.y += dy;
}
}
let point = Point(0, 0);
let point_ref = &point;
point_ref.move_by(5, 10);
PRINT point.x; // 5
PRINT point.y; // 10

The == operator only works on primitive types: Number, String, Boolean, Note, and NUL. Comparing arrays, dicts, class instances, or references with == is a type error, not false.

The type() function reports reference types with a & prefix:

let nums = #[1, 2, 3];
let nums_ref = &nums;
PRINT type(nums_ref); // &Array

References cannot be captured inside script pattern callbacks. Use plain variables (deep copies) instead.