| author | Alan Dipert
<alan@tailrecursion.com> 2026-11-10 20:16:33 UTC |
| committer | Alan Dipert
<alan@tailrecursion.com> 2026-11-10 20:16:33 UTC |
| parent | 9a8caa11a21b835accd016a65cde43330356227f |
| md/_MutableLocals.md | +3 | -13 |
diff --git a/md/_MutableLocals.md b/md/_MutableLocals.md index 71bfa40..6ab5626 100644 --- a/md/_MutableLocals.md +++ b/md/_MutableLocals.md @@ -59,7 +59,9 @@ Let's use a smaller program so we can see the machinery: ;; => 45 ``` -With the loop exposed, we can turn the program into operations and arrows: `setq` becomes a write, `if` chooses an edge, and `recur` leads back to the test. This is a [control-flow graph](https://www.cs.cornell.edu/courses/cs4120/2026sp/notes.html?id=traces). Each node is a _basic block_: a straight-line sequence of operations ending in a jump, a choice of successors, or an exit. Here is a simplified view, omitting temporary copies and the loop's discarded nil result: +With the loop exposed, we can turn the program into a [control-flow graph](https://www.cs.cornell.edu/courses/cs4120/2026sp/notes.html?id=traces). Each _node_ represents a _basic block_: a straight-line sequence of operations ending in a jump, a conditional branch, or an exit. Each _edge_ represents a possible transfer of control from one block to another, drawn as an arrow. + +`setq` becomes a write inside a block, `if` chooses between outgoing edges, and `recur` leads back to the loop's test. Here is a simplified view, omitting temporary copies and the loop's discarded nil result: ```mermaid flowchart TD @@ -196,15 +198,3 @@ To interrupt a function before it returns, you can throw something. [flambard/ta 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. 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. - -Back in Clojure, the practical differences are: - -| Library | Where changing state lives | Where a jump can go | -| --- | --- | --- | -| Blacktide | Lexical locals assigned with `setq`/`psetq` | An explicit label in the same compiled region | -| flambard/tagbody | Separate storage; its example uses `with-local-vars` | Through function calls to an enclosing `tagbody`, matched by label name | -| Farolero | Separate storage, such as atoms or volatiles | Through function calls to a particular active `tagbody` | - -Blacktide spends its compiler machinery on the problem we started with: updating our own little variables inside an algorithm. - -Both [tagbody](https://github.com/flambard/tagbody/blob/05352cd75cc218814cd98c3676f8cac26b0fe4e9/src/tagbody/Goto.clj) and [Farolero](https://github.com/IGJoshua/farolero/blob/6dbd299d25b37d22370a3134541bc49bb5c41273/src/java/farolero/signal/Signal.java) suppress stack-trace construction for their signals. A speed ranking needs benchmarks; “exceptions are slow” doesn't get to count as one.