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); its two gates pass on all fourteen chapters (public repo).
- Clojure — a re-telling aiming at roughly half the size (projected, not yet measured — see below).
- Scheme (Guile) — the re-telling closest to the book (the divide below is the evidence: exact rationals, cons cells).
- Hy — Lisp on Python's runtime, the re-telling that drifts furthest (the divide again: float division, no cons cells).
The three re-tellings are private, early scaffolds — chapter one worked end-to-end in each, no further. So the qualitative read-offs (closest, furthest) are grounded in the verified evidence below; the one quantitative claim, "half the size," is a projection, and marked as such.
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? / ? |
even? ok; mangled in Python view |
| state | setf / special vars |
atoms / refs / agents (rich) | set! + lexical |
Python mutation |
| recursion | stack; labels |
recur (explicit tail call) |
general 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
prediction is that the Clojure port lands near half the size, the saving
entirely deleted ceremony rather than cut material — but that number is a
projection, not a measurement: the ports are still chapter-one scaffolds, so
there is no fair line count yet. It is an estimate read off the bill of
materials above, to be checked when the ports fill in.
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).