Names & Live Editing
In a live session you don’t run a program once. You select a line here and a block there, in any order, while the music plays (see The Evaluation Model). The rules for names are built for that: a name means what you last executed for it, and what a piece of code means depends on what it says and on the session’s current state — never on who called it.
The last thing you executed wins
Section titled “The last thing you executed wins”The session has one namespace for everything you bind at the top level: lets, fn, class and
dsp definitions, and the names a use brings in. There is no ranking between them. Execute
fn Delay(…), then use "std/effects" { Delay };, and Delay is the effect; execute the fn
again and it is the function again. A call and a plain mention of the name always agree, because
they look in the same place.
That is the only rule you need while performing: to know what a name means, remember what you ran last. A hidden precedence — definitions over imports, say — would make the line you just executed silently do nothing.
A function sees the session, not its caller
Section titled “A function sees the session, not its caller”A named fn looks up any name that isn’t its own parameter or local in the session, when it is
called. A caller’s local variables never leak into it, or into a class method:
let tempo = 120;
fn beat_ms() { return 60000 / tempo;}
fn play(tempo) { // this tempo is local to play return beat_ms(); // beat_ms still reads the session's tempo}
assert play(999) == 500;print(beat_ms());This keeps each function readable on its own: to know what beat_ms does, read its body and the
session names it uses. If lookups followed the call chain instead, the same line would do different
things depending on which function you selected and ran it from — exactly the uncertainty
select-and-execute cannot afford.
Session lookups are live
Section titled “Session lookups are live”Because the lookup happens at call time, changing a session name changes every function that reads it. You don’t redefine the functions:
let tempo = 120;fn beat_ms() { return 60000 / tempo; }
tempo = 150;assert beat_ms() == 400; // picks up the new valueprint("live");This is what live coding leans on. Tweak a value, run that one line, and the instrument functions built on it follow. Redefining a function works the same way: every function that calls it gets the new version from its next call.
A closure freezes what it captures
Section titled “A closure freezes what it captures”Sometimes you want the opposite: a function that remembers a value as it was. That is what a
closure — a fn(…) { … } value — is for. It captures the variables it refers to, by value, at the
moment it is created:
fn make_adder(n) { return fn(x) { return x + n; }; // n is captured now}let add5 = make_adder(5);assert add5(3) == 8;print("frozen");What a closure freezes is the values it reads. A function it calls is looked up when the closure
runs, as in a named fn, so redefining that function reaches closures that already exist:
let f = fn() { return g(); };fn g() { return 1; }assert f() == 1;print("late");Run fn g() { return 2; } later in the session and f() returns 2. Reach for a named function or
a session value when behaviour should follow the session, and for a closure’s captured values when
it should hold on to one. The same
idea is why pattern generators
freeze what they capture: a stream that changed when an unrelated variable did could not replay the
same cycle twice.
A playing pattern follows your edits
Section titled “A playing pattern follows your edits”A function you give a pattern — p.map(fn(n) { … }), .map_with_timing(…), or a class’s
query(cycle) — is a closure like any other. The values it reads are frozen when the pattern is
made. The functions it calls are looked up each time the pattern computes a cycle:
let melody = [C4 E4 G4].map(fn(n) { return lift(n); });fn lift(n) { return n + 12; }print("follows");While melody plays, run fn lift(n) { return n + 7; } and it plays a fifth up instead, from the
next cycle it computes after that line has run. You don’t re-run the line that made the pattern.
So a playing pattern can also break: run a lift that calls a function that doesn’t exist, and
the callback fails each time it runs. A failing map callback plays its notes unchanged, and a
failing query rests. Either way the error is printed, once per run however often it fails, and
printed again after your next run if it is still failing:
A playing pattern's map callback failed: Undefined function 'h' at song.non:1:21 |1 | fn lift(n) { return h(n); } | ^^^^in the map callback: |4 | let melody = [C4 E4 G4].map(fn(n) { return lift(n); }); | ^^^^^^^^^^^^^^^^^^^It quotes the code as it was run, because by the time a pattern computes a cycle you may have moved those lines. For the same reason the error is not underlined in the editor.
One definition per name
Section titled “One definition per name”Within one piece of source you run, a name has one definition. Two fn greets, a fn tempo beside
a let tempo, a use and a dsp effect both binding Delay, or two methods of one name in a
class are an error, and nothing in that source runs.
Arity never chooses between functions; a parameter that may be left out gets a default value.
The reason is the same predictability. If tempo could be both a value and a function, or greet
could mean two bodies depending on how many arguments you passed, then reading a line would not
tell you what it does. Refusing the whole source, rather than running up to the clash, means an
execution never half-applies.
Running a binding again later is different, and it is the point of live coding: execute a new
fn greet and it replaces the old one from the next call on, while everything else keeps playing.
The rule is about one piece of source contradicting itself, not about changing your mind.
The editor follows the same line. Until you evaluate anything, it is in File Mode (offline):
the file is the piece of source, so a name it defines twice is marked. From your first evaluation
it is in Live Mode (online): the file is where you write selections, and a second fn greet
further down is the next version of greet, not a mistake. The status bar shows the mode; see
Live Mode and File Mode.
Definitions belong to the file
Section titled “Definitions belong to the file”fn, class and dsp definitions sit at the top level. A definition names something for the
whole file, so it is written where the whole file can see it; a helper that belongs to one place is
a closure bound with let. A definition exists from the line that defines it, so a call written
above a fn fails as undefined — unless an earlier run already defined it, which is the session
remembering.
The standard library sits underneath
Section titled “The standard library sits underneath”The standard library is a layer below the session. Any of its names can be bound in the session —
fn clamp, say — and from then on your code gets yours. The standard library itself never
sees the session: its functions keep calling each other, so let round = 0; cannot break a
function that rounds inside. Overriding a name is a decision about your code, and only about your
code.
A module keeps to itself
Section titled “A module keeps to itself”A module’s code sees its own top-level names and the standard library, never the session that
imports it. A library behaves the same in every piece that uses it, and a let you happen to write
in your piece cannot change what it does. What a module needs from you, it takes as arguments.
Editing a module reaches the session
Section titled “Editing a module reaches the session”A use evaluates a module when its source, or the source of anything it imports, differs from
what the session last evaluated. So you edit a library file, run your use line again, and the new
definitions are there — there is no reload gesture to remember. And when nothing changed, nothing
runs again: a module that builds a track at its top level builds it once, not on every unrelated
execution. Freshness follows the imports, so editing a module that another one imports refreshes
both.
RESET; is the full reload, for when you want everything evaluated from scratch.
Next Steps
Section titled “Next Steps”- Variables & Scope — the rules, in brief
- Modules — module evaluation in the reference