git » homepage.git » commit f3a700b

Refine the mutable-locals article and restore its JACL citation

author Alan Dipert
2026-11-11 00:06:03 UTC
committer Alan Dipert
2026-11-11 00:06:03 UTC
parent 03ed7910417ea5c002b90be9cd9c2127817a69d2

Refine the mutable-locals article and restore its JACL citation

Tighten the introduction, name the loophole macro directly, and remove the parentheses aside. Identify the longer JACL paper as a draft and credit the earlier Valtan optimization.

Recover the missing original six-page PDF from https://s3.amazonaws.com/tailrecursion.com/~alan/documents/jacl-els-2020-draft.pdf and preserve its former public path.

Validation: 23 article links reviewed; pinned site build and all 31 tests passed.

md/_MutableLocals.md +4 -6
md/documents/jacl-els-2020-draft.pdf +0 -0

diff --git a/md/_MutableLocals.md b/md/_MutableLocals.md
index fa53c40..37a9152 100644
--- a/md/_MutableLocals.md
+++ b/md/_MutableLocals.md
@@ -29,9 +29,7 @@ And that gives us a way in. [`recur`](https://clojure.org/reference/special_form
 
 Anyway, those local-variable stores under the hood are the keys to the kingdom. We can write assignments where we want them, then compile them into ordinary Clojure that does the updating for us, via `recur`. We can automate the mental gymnastics we usually perform when translating an imperative algorithm.
 
-A macro can turn our assignments and jumps into the right combination of lexical bindings, loops, and `recur` forms, and Clojure can do the rest. No atoms, arrays or other mutable reference cells to hold our locals, and no changes to Clojure's compiler.
-
-Doing this for a useful subset of Clojure turns out to involve writing a small compiler. Apparently I'm willing to do that. The result is Blacktide and its `loophole` macro. **Mutable local variables (and goto!) to the people!** Allow me to give you the tour.
+Doing this for a useful subset of Clojure turns out to involve writing a small compiler inside a macro. Apparently I'm willing to do that. The result is Blacktide and its `loophole` macro. **Mutable local variables (and goto!) to the people!** Allow me to give you the tour.
 
 ## Compiling to a state machine
 
@@ -179,13 +177,13 @@ For our sum with the early exit, it emits this, with shorter local names. `loop*
 
 Both loop variables are primitive longs. A value-analysis pass proves that each addition produces a long or throws on overflow; a separate storage pass checks that no use observes the intermediate boxes' identities. Only then does numeric lowering select the primitive overloads, spelled out here by `:param-tags`. The emitter gets those decisions already made.
 
-The [`RT.box(Object)`](https://github.com/clojure/clojure/blob/clojure-1.12.6/src/jvm/clojure/lang/RT.java#L1037-L1039) calls are at the exits: Clojure boxes the final long, and `RT.box(Object)` returns that object unchanged. The loop's arithmetic stays unboxed. Putting this boundary around the whole loop would make Clojure generate an extra function to evaluate it. Apparently even the parentheses need supervision.
+The [`RT.box(Object)`](https://github.com/clojure/clojure/blob/clojure-1.12.6/src/jvm/clojure/lang/RT.java#L1037-L1039) calls are at the exits: Clojure boxes the final long, and `RT.box(Object)` returns that object unchanged. The loop's arithmetic stays unboxed. Putting this boundary around the whole loop would make Clojure generate an extra function to evaluate it.
 
 When the index reaches five, the inner `if` returns the sum: 10. That was our `goto`. Having gone to considerable trouble to accommodate it, we have compiled it out of existence.
 
 ## Back to the inversion counter
 
-Back at the inversion counter, we can write `(setq n (inc n))` where the counting happens, inside the nested loops. [Blacktide](https://tailrecursion.com/repos/r/blacktide.git/b/master/t/f=README.md.html) figures out the bindings and `recur` arguments needed to carry that value forward. Our assignment becomes the kind of update Clojure already knows how to compile.
+Back at the inversion counter, we can write `(setq n (inc n))` where the counting happens, inside the nested loops. [`loophole`](https://tailrecursion.com/repos/r/blacktide.git/b/master/t/f=README.md.html) figures out the bindings and `recur` arguments needed to carry that value forward. Our assignment becomes the kind of update Clojure already knows how to compile.
 
 To think, all this time, mutation was sitting inside `recur`, wearing a little hat that said FUNCTIONAL PROGRAMMING...
 
@@ -197,6 +195,6 @@ To interrupt a function before it returns, you can throw something. [flambard/ta
 
 [Farolero](https://github.com/IGJoshua/farolero) uses thrown signals too. A callback can jump out through intervening function calls, running `finally` cleanup along the way. This is a _nonlocal exit_. Farolero's [`tagbody`/`go` implementation](https://github.com/IGJoshua/farolero/blob/6dbd299d25b37d22370a3134541bc49bb5c41273/src/cljc/farolero/core.cljc#L144-L196) feeds those signals into a `loop`/`case` dispatcher and checks that an escape targets the right, still-active execution of `tagbody`. It also supplies `block`/`return-from`, all within a larger system of Common Lisp-style conditions and restarts: tools for reporting problems and choosing how to recover.
 
-A jump that stays in one function gives the compiler another option. My [2020 JACL paper, §3.3.3](https://tailrecursion.com/~alan/documents/jacl-els-2020-draft.pdf#page=4) describes compiling Common Lisp's local `GO` operations into JavaScript's labeled `continue`. If all jumps to a `TAGBODY` are local, its generated `try`/`catch` disappears too.
+A jump that stays in one function gives the compiler another option. My [2020 JACL draft, §3.3.3](./documents/jacl-els-2020-draft.pdf#page=4) describes compiling Common Lisp's local `GO` operations into JavaScript's labeled `continue`. If all jumps to a `TAGBODY` are local, its generated `try`/`catch` disappears too. I later learned that [cxxxr's Valtan](https://github.com/cxxxr/valtan/blob/8d8ba20ded78f53502a6004609b29bb50872f866/library/valtan-core/compiler/hir-optimize.lisp#L237-L284) already optimized local jumps, and shortened the final paper accordingly.
 
 That's the division of labor I'd want in a fuller `tagbody`: use Blacktide-style compilation wherever a jump can become ordinary control flow, and reserve exception unwinding for exits through function calls. Both paths still owe us our `finally` blocks. Blacktide currently sticks to its compiled region.
diff --git a/md/documents/jacl-els-2020-draft.pdf b/md/documents/jacl-els-2020-draft.pdf
new file mode 100644
index 0000000..a3021c1
Binary files /dev/null and b/md/documents/jacl-els-2020-draft.pdf differ