Values & Types
Resonon is gradually typed: you never have to write a type, and an annotation you do write is a runtime contract. An untyped variable can hold any value; a typed variable checks every write. Operators on values are in Operators; collections are in Collections & Iterators. Why containers share and primitives copy is in Values & Sharing.
Comments
Section titled “Comments”| Syntax | Kind |
|---|---|
// … | Line comment |
/* … */ | Block comment |
/// … | Doc comment for the definition below it |
//! … | Doc comment for the file; only at the top of the file |
//// … | Line comment (four or more slashes) |
//! File documentation — what this file is.
// Line comment/* Block comment */
/// Doubles a number.fn double(x) { return x * 2;}print(double(4));Doc comments are shown by show(), editor hover, and resonon pkg inspect.
///documents the nextfn,let,class, class method,interface, interface method,enum, enum variant ordspdefinition. Blank lines and ordinary comments in between change nothing; consecutive///blocks join into one.- A
///with nothing below it to document — above ause, an expression, a class field, inside a block, or at the end of the file — is an error. - A
//!anywhere but the top of the file is an error. - A doc comment with an
Experimental:line marks what it documents as experimental, with the text after it saying what may change:show(), editor hover and completion say so. In a file’s//!, it marks everything the file defines.
Docstring template
Section titled “Docstring template”Use a short summary, describe the arguments and result, and include a working example. Put the types in the declaration; a type written only in a comment does not guard a value. This template uses the same section headers as the standard library:
/// Transposes a note by a number of semitones, or keeps an absent note absent.////// Parameters:/// root: Note? — the note, or NUL for no note/// steps: Number — semitones to transpose; defaults to one octave/// Returns:/// Note? — the transposed note, or NUL when root is absent/// Examples:/// transpose_note(C4, 7)/// transpose_note(NUL)fn transpose_note(root: Note?, steps: Number = 12) -> Note? { if root == NUL { return NUL; } return root + steps;}assert transpose_note(C4, 7) == G4;assert transpose_note(NUL) == NUL;Keep comment types identical to the declaration, including ?, unions and collection element
types. Use Parameters: for arguments, Returns: for the result and Example: or Examples:
for examples. Visual operators also use Uniforms: and Inputs: for controls and texture inputs;
these are separate from their constructor arguments.
Literals
Section titled “Literals”| Type | Literals |
|---|---|
| Number | 42, 3.14, .5, 5., 1e-3, 2.5e2, 0xFF, 0b1010, 0xFF_FF, 0b1010_0011 |
| String | "text", f"text {expr}" |
| Boolean | true, false |
| NUL | NUL |
| Note | C4, F#3, Bb2, C-1 … G9 |
| Rest | _ |
| Pattern | [C4 E4 G4], <C4 E4>, {C4 D4 E4} — see Mini-Notation Grammar |
| Array | #[1, 2, 3] |
| Dict | #{"key": 1, 36: "kick"} |
| Tuple | (1, "a") |
| Function | fn(x) { return x * 2; } |
Numbers
Section titled “Numbers”There is one Number type: 5 and 5.0 are the same value. Hex and binary literals accept _
separators.
Strings
Section titled “Strings”Strings are double-quoted and immutable; every string method returns a new string. The methods are
in the String reference.
| Escape | Character |
|---|---|
\" | Double quote |
\\ | Backslash |
\n | Newline |
\r | Carriage return |
\t | Tab |
An f prefix makes a format string: any expression inside {…} is evaluated and formatted as
print() would show it. {{ and }} are literal braces. String literals may appear inside the
braces.
let vel = 100;let notes = #["C4", "E4", "G4"];print(f"Dynamics: {if vel > 80 { "loud" } else { "soft" }}");print(f"Root: {notes[0]}, count: {notes.length()}");print(f"Literal braces: {{like this}}");assert "Tempo: " + 120 == "Tempo: 120"; // + also concatenatesA string is not directly iterable: for ch in "abc" is an error; iterate "abc".iter(), which
yields one-character strings.
Booleans and truthiness
Section titled “Booleans and truthiness”if conditions, match guards, assert, &&, || and ! require an actual
Boolean; any other value is a type error. Where a callback’s result is tested — .filter(),
.find(), .any(), .all() — only false and NUL are falsy, and everything else, including
0, "" and #[], is truthy.
print(#[0, "", false, NUL, #[]].filter(fn(x) { return x; })); // [0, "", []]NUL is the absence of a value.
NUL == NUListrue;NULequals nothing else (not0, notfalse).NULcannot be ordered (NUL < 1) or used in arithmetic, except that"text" + NULgives"textNUL".- A function without a
returnreturnsNUL. Iterators returnNULonce exhausted, anddict.get(key)returnsNULfor a missing key. - Reading a variable that was never defined is an error, not
NUL. NULcan be stored in arrays, dicts and tuples.
A note literal is a letter, an optional accidental (# or b), and an octave from -1 to 9.
Notes map to MIDI 0–127 (C-1 to G9, C4 = 60); a note outside that range is a parse error.
Notes compare with Numbers by MIDI value. A name like Cs2 is not a note; it parses as an
identifier.
assert C4 == 60;assert A4 >= 69;assert Bb3 == A#3;print(C-1); // the lowest MIDI noteprint(G9); // the highestType annotations
Section titled “Type annotations”An annotation is a contract. A value is checked against it where the value crosses it, and a value that does not fit is a type error at that place. The language server uses annotations for signatures, hover, completion and diagnostics too. The spelling rules for NUL below are checked when the code is read.
| Where | Syntax |
|---|---|
| Parameter | fn f(x: Number) |
| Return | fn f() -> Number |
| Method returning its receiver’s type | fn clone() -> Self |
| Union | Number | String |
| Optional | Number? — a Number or NUL |
| Function type | (Number) -> Number |
| Collection | Array<Note>, Dict<String, Number>, Iterator<Event> |
| Grouped type | ((Number) -> Number)? |
| Class field | let speed: Number; |
| Tuple destructuring | let (f, l): (Number, String) = (440, "A4"); |
| Variable | let x: Number = 1; |
fn apply(f: (Number) -> Number, x: Number | String) -> Number { return f(x);}print(apply(fn(n) { return n * 2; }, 5)); // 10Where a value is checked
Section titled “Where a value is checked”- Arguments. Each argument, and each default a call leaves to a parameter. A rest parameter’s
type is the type of each value it collects:
...notes: Note. - Returns. The value a
returngives, and NUL when the body runs to its end. - Variables.
let x: Tmakes a typed variable: its value and every later write fitT, a compound write (x += 1) and a closure’s write included. A typed parameter is a typed variable too.let (a, b): (A, B)types each name. A laterlet x = …without a type makesxuntyped again. - Fields. Every write to a typed field. When an instance is made, each typed field that cannot be
NUL must be set, by
new.
An unannotated binding adds no type contract; operations still follow their own type rules. A pattern callback checks the same annotations, on the thread that plays it.
let tempo: Number = 120;fn faster() { tempo += 5; }faster();assert tempo == 125;
let sound = C4; // no contract: the next value may have another typesound = "kick";assert sound == "kick";
let tempo = "fast"; // a fresh unannotated let removes the previous contractassert tempo == "fast";let tempo: Number = 120;tempo = "fast"; // Type error: tempo is a Number; got the String "fast"Conversions
Section titled “Conversions”A class can declare how a value of another type becomes one of its values, with
fn from. Where an annotation asks for the class
and a value does not fit it, the class’s from converts the value once, at that place. A MIDI
number converts to a Note: 60 is C4, and a fraction is microtonal.
let n: Note = 60.5; // a quarter-tone above C4fn up(x: Note) -> Note { return x + 12; }assert up(60) == C5;print(chord(60, "maj"));- A value that fits as it is is not converted.
x: Note | Numbergiven60keeps the Number. - Nothing inside a collection converts. An
Array<Note>given#[60]is a type error.Array<Note | Number>takes MIDI numbers as they are. - Operators do not convert, and a conversion never leads to another one.
- When the
froms of two members of a union both take a value, it is a type error that names both. - When
fromrefuses a value, the error is at the place that asked for the conversion:up: x expects a Note; the Number inf does not convert to a Note: a MIDI note number is finite.
Collection types
Section titled “Collection types”Array<T> checks every element against T. Dict<K, V> checks every key against K and every
value against V; its key type is String, Number, or String | Number. Collections and tuples
nest: Array<(Note, Number)> and Dict<String, Array<Number>> are valid types.
Bare Array, Dict, and Iterator mean Array<Any>, Dict<String | Number, Any>, and
Iterator<Any>. Only these three builtin types take type arguments. Array<Note | Number> accepts
both note literals and MIDI numbers; collection checks preserve the original elements.
let velocities: Array<Number> = #[80, 100];let notes: Array<Note | Number> = #[C4, 64, G4];let controls: Dict<String, Number> = #{"cutoff": 1200, "resonance": 0.3};let steps: Iterator<Number> = velocities.iter();assert steps.next() == 80;Checks happen at annotated boundaries. An array, dict, or tuple is checked through its nested
contents; an empty collection fits any element type. Shared collections can still change after a
check, and crossing another annotated boundary checks their contents again. An Iterator<T> is
checked by kind without pulling values from it; its lazy values are checked by their own
annotations when they run.
Parentheses group types: ((Number) -> Number)? is an optional function, and
(Number) -> (Number | String) is a function returning either type.
Optional types
Section titled “Optional types”T? is a value of type T, or NUL. The ? applies to the whole type it ends: Note | Number? is a
Note, a Number or NUL. (Number) -> Number? is a function that returns an optional Number.
- NUL in a type is written with
?.Number | NULis an error that names the fix,Number?. Anyalready includes NUL, soAny?is an error.NUL?is an error too.-> NULalone stays legal.- A parameter that defaults to NUL takes NUL, so its type says so:
x: Number? = NUL. A parameter writtenx: Number = NULis an error.x: Any = NULandx = NULstay legal.
fn transpose(root: Note | Number? = NUL, steps: Number = 12) -> Note | Number? { if root == NUL { return NUL; } return root + steps;}print(transpose(C4)); // C5print(transpose()); // NULA name in an annotation is a type where the annotation is written:
Number,Boolean,NUL,Rest,FunctionandAny, everywhere.Self, only as the whole return type of a method:fn clone() -> Self. It is the type of the value the method is called on, so#[1, 2].clone()is anArray<Number>and a class’sclone()is that class.Self?,Array<Self>andSelfin any other annotation are errors.- The classes, interfaces and enums of the file, and the ones it imports.
- The types of the prelude, such as
String,Note,Pattern,AudioTrackandSignal.
A type of a module, such as Sampler, needs a use of its module. The editor marks a name that is
not a type as unknown type.
Interfaces and enums
Section titled “Interfaces and enums”An interface annotation accepts values of classes that explicitly declare implements for that
interface. Merely defining matching methods does not implement it. For example, a script pattern
says class Arp implements Pattern. See Interfaces
and Script Patterns.
An enum annotation accepts only that enum’s variants, rather than a String with the same spelling:
enum Articulation { short, long }fn gate(style: Articulation) -> Number { return if style == Articulation.short { 0.25 } else { 0.9 };}assert gate(Articulation.short) == 0.25;See Enums for matching and live editing.
type()
Section titled “type()”type(value) returns the type’s name as a String; for a class instance, the class name; for an
enum variant, the enum name.
assert type(42) == "Number";assert type("hi") == "String";assert type(true) == "Boolean";assert type([C4 D4]) == "Pattern";assert type((1, 2)) == "Tuple";print(type(#[1]));