I've been recently obsessed with the writing of Douglas Hofstadter, and he spent three of his Scientific American columns in 1983 teaching Lisp (the first, "Lisp: Atoms and Lists", survives online), later collected in Metamagical Themas. He opens with a mission statement:
Why is most AI work done in Lisp? There are many reasons, most of which are somewhat technical, but one of the best is quite simple: Lisp is crisp. Or as Marilyn Monroe said in The Seven-Year Itch, "I think it's just elegant!"
— Douglas Hofstadter, "Lisp: Atoms and Lists" (1983)
It's no coincidence that the author of the great book about self-reference (Gödel, Escher, Bach) fell for this language: Lisp is probably the most GEB-shaped artifact in computing.
The engine of GEB is Gödel numbering: encoding statements about arithmetic as arithmetic, painstakingly numbering every symbol until number theory could be made to talk about itself. (For a gentle tour of how the proof uses it, see Quanta's explainer; for this post, the gist is enough.)
It took a stroke of genius to build that bridge, because sentences and numbers live in different worlds. In Lisp, it seems, the bridge comes built-in, as the sentence already is the data structure. Hofstadter saw the temptation, and near the end of GEB he stages this exact argument, letting the Crab assume the burden of proof, in the book's most enchanting 'fugue':
Well, in the programming language LISP, you can talk about your own programs directly, instead of indirectly, because programs and data have exactly the same form. Gödel should have just thought up LISP, and then—
— the Crab, in Gödel, Escher, Bach (20th-anniversary ed.), p. 738
The Crab is making this post's argument: programs and data have exactly the same form, and quote is precisely the formalized quotation he goes on to wish Gödel had invented. But!
But the Author (Hofstadter) interrupts him:
Author: …no reference is truly direct — every reference depends on SOME kind of coding scheme. It's just a question of how implicit it is. Therefore, no self-reference is direct, not even in LISP.
Hofstadter is right, of course. Look under a quoted form and there is still a code: reader syntax, interned symbols, and cons cells laid out in memory. Lisp didn't abolish Gödel's bridge, but arguably simplified it for the programming use case. It built the bridge so well, and sank it so deep beneath the syntax, that you can cross it naively.
A strange loop is Hofstadter's coinage, and GEB defines it in its opening pages:
"The 'Strange Loop' phenomenon occurs whenever, by moving upwards (or downwards) through the levels of some hierarchical system, we unexpectedly find ourselves right back where we started."
Escher's Drawing Hands is his canonical image of this phenomenon. In this hand-drawn drawing of drawing hands, each hand draws the hand that is drawing it, and is both sketcher and sketch at once. Elisp hides the same lithograph in its bootstrap. defmacro, the form you use to create macros, is itself a macro. The hand that draws hands is drawn; the macro that defines macros is a macro.
Figure 3: M.C. Escher, Drawing Hands (1948). Each hand draws the hand that draws it. (macrop 'defmacro) ⇒ t. © The M.C. Escher Company.
If the Drawing Hands image has you thinking about macros, know that the loops nest. A macro can expand into code that contains more macro calls — remember that defun hiding inside deftoggle? Take it one story higher:
(defmacro deftoggles (&rest vars)
"Define a toggle command for each variable in VARS."
`(progn ,@(mapcar (lambda (v) `(deftoggle ,v)) vars)))
(deftoggles debug-on-error truncate-lines)
;; ⇒ (progn (deftoggle debug-on-error) (deftoggle truncate-lines))
;; ⇒ ... (defun chiply/toggle-debug-on-error () ...)
;; ⇒ ... (defalias 'chiply/toggle-debug-on-error #'(lambda () ...))
A program writing a program writing a program writing a program. Hofstadter's running metaphor for the Lisp interpreter is a genie granting wishes, and even while introducing the language's basics, having just shown the reader that Lisp statements are themselves lists, he spots exactly this loop:
…the Lisp genie, by manipulating lists and atoms, can actually construct new wishes by itself. Thus the object of a wish can be the construction — and subsequent evaluation — of a new wish!
— Douglas Hofstadter, "Lisp: Atoms and Lists" (1983)
A macro is precisely that: a wish whose object is a new wish.
This kind of macro expansion makes me think of Escher's Print Gallery, where a young man stands in a gallery looking at a print of a seaport, and the print swells outward until it contains the gallery, and the young man, inside it. Each level of a macro expansion is a picture that turns out to contain the room you were standing in.
Escher famously couldn't finish this paradox. At the center of the lithograph, where the loop closes on itself, he left a blank patch and signed his name. The Elisp tower has its blank patch too. When you expand all the way down, you bottom out at the special forms, where the language stops being written in itself and things move over to C.
Figure 4: M.C. Escher, Print Gallery (1956). The print contains the gallery that contains its viewer; at the center, where the loop closes, Escher left a blank patch and his signature. © The M.C. Escher Company.
The program–data dualism has its own lithograph: Reptiles, where a lizard crawls out of a flat sketchbook drawing, climbs up over a book and a dodecahedron as a living, three-dimensional creature, and then climbs back into the page to become a drawing again. That is quote and eval exactly. A quoted form is the lizard on paper (inert, flat, safe to handle), whereas eval is when it climbs off the page and comes to life. Macros do their work on the paper lizards, rearranging drawings that will shortly be alive, their hearts beating in the Lisp interpreter.
Figure 5: M.C. Escher, Reptiles (1943). Off the page, around the desk, back onto the page. This is eval and quote as lithograph. © The M.C. Escher Company.
There's one more Hofstadter obsession that Lisp exhibits. GEB's deepest question is how meaning condenses out of meaningless symbol-shuffling, layer by layer. Lisp is unabashed about the importance of symbols here because its atoms are literally called symbols. Symbols are Elisp's first-class objects you can pass around, compare, and define (deftoggle interned one for you). And each floor of the expansion tower speaks its own language: the use-package form speaks configuration, its expansion speaks hooks and keymaps, and the floors below speak control flow (special forms again), until meaning has condensed all the way into machine operations. No floor is the "real" one. Instead, the whole thing is a tangled hierarchy that you can inhabit. With Emacs, you can ride up and down at will. Here's how.