A Gentle Introduction, Four Lisps: One Book, Four Re-tellings
Table of Contents
1. One book, four Lisps
This is a point-in-time note, and the question is narrow: when you re-tell the same gentle introduction to symbolic computation in four Lisps, what does each one actually buy — the student reading it, and the maintainer keeping it green?
The artifact is Touretzky's A Gentle Introduction to Symbolic Computation (Touretzky 1990) — recursion, lists, closures, macros, code-as-data. It has been re-told four times, each a working repo with two passing gates on chapter one:
- Common Lisp — the origin (the book's own dialect), verified end-to-end.
- Clojure — a re-telling at roughly half the size.
- Scheme (Guile) — the re-telling closest to the book.
- Hy — Lisp on Python's runtime, the re-telling that drifts furthest.
The four implementations already exist; they are the evidence. This is the same move as reading four type disciplines off one reversible-transform tool (one tool, four ways) — one artifact, N lenses.
2. One divide, and most of the story
Chapter one defines average — Touretzky's (/ (+ a b) 2) — and the helper
half. Evaluate (half 5) in each:
| Lisp | (half 5) |
why |
|---|---|---|
| Common Lisp | 5/2 |
rational — the numeric tower is intact |
| Clojure | 5/2 |
exact Ratio |
| Scheme/Guile | 5/2 |
exact rational |
| Hy | 2.5 |
Python's float division — the tower is Python's |
Three exact, one float. That single line is the thesis in miniature: the numeric tower is a language-family property, and Hy — Lisp syntax over Python's runtime — does not inherit it. Everything a Python-native reader gains (the ecosystem, familiarity) is paid for here, in the first arithmetic example, with a rounding the other three never do. Nothing about the parentheses tells you this; only running it does.
3. What each Lisp buys — read off the axes
| axis | Common Lisp | Clojure | Scheme (Guile) | Hy (on Python) |
|---|---|---|---|---|
| numbers | rational tower | rationals + BigInt |
rational tower | Python floats / ints |
| lists | cons cells | persistent vectors & lists | cons cells | Python list (no cons) |
| predicates | evenp / -p |
even? / ? |
even? / ? |
? mangles from Python |
| state | setf / special vars |
atoms / refs / agents (rich) | set! + lexical |
Python mutation |
| recursion | stack; labels |
recur (TCO), lazy seqs |
tail calls (real TCO) | Python recursion limit |
| macros | defmacro + gensym |
defmacro + gensym (~#) |
hygienic syntax-rules |
defmacro → Python AST |
| test framework | hand-rolled + loader | clojure.test |
SRFI-64 | Hy-native asserts |
| lint / static | custom org checkers | clj-kondo |
guild compile |
hyc compile |
| visibility tool | built by hand (traces) | Portal / FlowStorm / tap> |
REPL + ,trace |
hy2py (read the Python) |
| build | org-tangle + ASDF/SBCL | deps.edn + bb |
make + modules |
pyproject + hyc |
Two read-offs fall out of the table.
For the student. Hy sells reach — "Lisp rides on the Python you already run" — and charges the numeric tower and cons cells for it; it is the on-ramp, not the destination. Guile sells fit — the SICP heartland (Abelson, Sussman, and Sussman 1996), nothing between the reader and the ideas, hygienic macros the origin can't teach. Clojure sells a state model — atoms, refs, and agents make the chapter Common Lisp couldn't (managed identity), and persistent data changes what "a list" even means. Common Lisp is the fullest and the heaviest onramp — the reference, not the recommendation.
For the maintainer. Read the bottom half of the table as a bill of materials.
The Common Lisp companion hand-builds what the others get for free — an org
tangle pipeline, an ASDF system per chapter, a (check) macro and a loader, two
custom lint checkers, its own tracer. Each re-telling deletes that column and
adopts the standard thing (clojure.test, SRFI-64, clj-kondo, Portal). The
Clojure port came out at ~half the size, and the halving is entirely deleted
ceremony, not cut material.
4. The method, and what stays constant
Same shape as the type-systems note: hold the artifact fixed, vary one dimension, and the differences that survive are the ones that matter. Here the fixed artifact is a pedagogical arc and the varied dimension is the Lisp; there it was a codec and the type discipline.
What does not vary is the discipline. Every re-telling is built the same way:
each unit is simultaneously something to read, something to run, and something
that checks itself; two orthogonal gates (documents + code); one chapter at a
time, green before moving on; verify by running, not reasoning. The mechanism
of verification changes with the language — hand-rolled, clojure.test, SRFI-64,
Hy asserts — but the act does not. That constancy is the point: a language
buys you a numeric tower or a macro hygiene or a state model, but it does not buy
you the assurance that your claims are true. You buy that by seeing them run,
in whichever dialect (visibility is verification).