| author | Alan Dipert
<alan@tailrecursion.com> 2026-11-11 00:06:03 UTC |
| committer | Alan Dipert
<alan@tailrecursion.com> 2026-11-11 00:06:03 UTC |
| parent | 03ed7910417ea5c002b90be9cd9c2127817a69d2 |
| 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