From a8a032e3406380f5d3332fd7dfa567eff59fb3dd Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 12 Sep 2026 13:51:40 +0700 Subject: [PATCH] Three roles want three words, and two of them want the same dependency tracking --- DISCUSS.md | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/DISCUSS.md b/DISCUSS.md index dc94160..aba96b2 100644 --- a/DISCUSS.md +++ b/DISCUSS.md @@ -68,18 +68,41 @@ function behind an indirection cell, or a constant by its source name, needs no information the daemon already has. Writing a disassembler is not the interesting part and should stay off the table; richer annotation of objdump's output is cheap and is where SBCL's advantage actually comes from. -## 3. `def`, `defvar`, `defconst` +## 3. `def`, `defvar`, `defconst` — three roles, currently two words -There is no `def`. There is `defvar` (mutable global) and `defconst` (compile-time constant, folded). +There is no `def`. `defvar` is a mutable global that is always re-initialised on redefinition; `defconst` is a +compile-time constant, folded into its use sites. -The proposal: a `def` that is mutable, where `defvar` only rewrites the value if the variable is *new* — Common Lisp's -actual `defvar` semantics, where re-evaluating a `defvar` deliberately does not clobber a value you have been building up -at runtime. That distinction matters much more here than in most languages, because `C-c C-k` on a whole buffer -re-evaluates every top-level form against a *running* program, and today that resets state you may have spent a session -accumulating. +The problem in practice: `C-c C-k` re-evaluates every top-level form against a *running* program, so today it wipes +state you may have spent a session accumulating. -`defconst` under redefinition is the unclear one, as noted — it is folded into its use sites, so changing one is closer to -a recompile than to an assignment. +**Settled in conversation — three roles, three words:** + +- **`def`** — always re-initialised. The tweakable: some number you are adjusting and do not care about preserving. +- **`defvar`** — re-initialised *only if it would come out different*. For heavier initialisers: an N×M grid that should + recompute only when the rows or columns actually changed. +- **`defconst`** — folded, and reserved for what genuinely cannot change for the life of the program. `rows`/`cols` in + `sand.flan` are honest `defconst`s: the program does not support resizing on the fly and changing them means resetting + everything anyway. + +Note this **`defvar` is not Common Lisp's**, which is "assign only if unbound". This one is value-dependent, which suits +a live-edited game better and is the harder of the two to implement. + +**The open question is what "different" means**, and the grid example is what makes it sharp. If the grid is sized by +`rows` and `cols`, the initialising expression's *text* is unchanged when `rows` changes — only its value is. So: + +1. **Compare the expression** (textual or AST). Cheap, and does not catch the case above. +2. **Compare the expression plus everything it depends on.** Catches it. Needs dependency tracking. +3. **Evaluate and compare the result.** Correct, and defeats the entire purpose whenever the initialiser is expensive — + which is the only case this feature exists for. + +Option 2 is what is wanted. + +**The reason to do it: it is the same machinery `defconst` needs.** A folded constant cannot be tuned live, because its +value is baked into every function that used it — so the values you most want to tweak while the game runs are exactly +the ones you cannot. Fixing that means knowing which functions depend on which constants, and rebuilding those. That is +the same dependency tracking `defvar`'s "if different" requires. Built once, it buys live-tunable constants *and* the +conditional re-initialisation. Neither is worth building alone; together they are clearly worth it. ## 4. Structural typing, row polymorphism, anonymous structs