Values & Sharing
A few choices about values shape how resonon code behaves: which values are shared, how strict the types are, and what an annotation promises. Each follows from the same aim — code you can select and run in any order, without being surprised by what it does.
Containers share, primitives copy
Section titled “Containers share, primitives copy”Assigning a container — an array, a dict or a class instance — does not copy it. Both names then refer to the same data, and a change through one is seen through the other. Passing a container to a function works the same way:
let a = #[1, 2, 3];let b = a;b.push(99);assert a.length() == 4; // a sees the change
fn append_high(arr) { arr.push(C5);}let melody = #[C4, E4, G4];append_high(melody);assert melody.length() == 4; // the caller's array changedprint(melody);This is the rule the rest of a session relies on. Tracks, instruments, effects, MIDI tracks and
visuals are shared handles too: let lead = drums; gives the same track a second name, it does not
build a second track. A DAW works the same way — a track is one object however many places refer to
it — and if assignment copied, a helper function that configured a track would configure a copy
that nobody hears.
When you do want an independent copy, say so with .clone():
let a = #[1, 2, 3];let b = a.clone();b.push(99);assert a.length() == 3; // unchangedprint(b);Primitives — Numbers, Strings, Booleans, Notes and NUL — are always copied, so there is
nothing to share. Strings and tuples cannot be changed at all: every string method returns a new
string, and a tuple has no push or element assignment. A value that cannot change behaves the same
whether it is shared or copied.
Changing in place, or returning a new value
Section titled “Changing in place, or returning a new value”A class method can change this, or it can return a new instance and leave this alone:
class Counter { let count; fn new(n) { this.count = n; } fn increment() { this.count += 1; } // changes in place fn incremented() { return Counter(this.count + 1); } // returns a new one}
let c = Counter(0);let alias = c;c.increment();assert alias.count == 1; // the alias sees the change
let d = c.incremented().incremented();assert c.count == 1; // c is untouchedprint(d.count); // 3Both are legitimate; the class you write decides. Patterns always take the second form: every
pattern method returns a new pattern, which is why one motif can feed any number of variations (see
How Patterns Work).
Returning new values makes chains like c.incremented().incremented() safe to write, because no
link in the chain changes something another name is holding.
Types are strict
Section titled “Types are strict”Resonon does not quietly convert one type into another. 5 + "5" concatenates because one side is
a String, but C4 + C4, true && 0 or NUL + 1 are type errors, not a best guess. The cross-type
rules that exist are few and each has a musical reason: a Note compares with a Number by MIDI value
(C4 == 60), + and - transpose a pitch, and * and / scale its frequency. They are listed in
the type-compatibility table.
The same thinking separates conditions from truthiness. An if, a match guard or an
assert needs a real Boolean, so if count { } is an error rather than a silent test for zero —
you write what you mean, if count > 0. Only where a callback’s answer decides whether to keep an
element (.filter(), .find(), .any(), .all()) does truthiness apply, and there only false
and NUL are falsy. 0, "" and #[] are real values, and treating them as “nothing” is a
classic source of dropped notes.
NUL means “no value”, and it stays that way. It does not propagate through arithmetic the way SQL’s
NULL does: using it as an operand is an error at the line that did it, not a NUL that surfaces
three functions later. Reading a variable that was never defined is an error too, so a typo shows
up where you made it.
Annotations document, they don’t enforce
Section titled “Annotations document, they don’t enforce”A type annotation (fn f(x: Number) -> Number) is a contract for readers and for the editor, which
uses it for signatures, hover and completion. It is never checked at run time: resonon is
dynamically typed on purpose, so that a value can be whatever the moment needs while you are
performing.
Annotations earn their place where a shape is fixed — function parameters and returns, class
fields. A variable has no fixed type (its type is whatever it holds now), so an annotation on a
let describes nothing the editor doesn’t already know from the value; by convention it is left
out. Where the two disagree, the editor shows the value’s real type.
Next Steps
Section titled “Next Steps”- Values & Types — every literal and the truthiness rules
- Names & Live Editing — how names resolve in a running session