Gödel, Escher, Elisp: The Beauty of Macros | Charlie Holland's Blog
Skip to main content<br>Gödel, Escher, Elisp: The Beauty of Macros<br>Gödel, Escher, Elisp: The Beauty of Macros<br>August 4, 2026
© 2026 Charlie Holland
Gödel, Escher, Elisp: The Beauty of Macros
Gödel, Escher, Elisp: The Beauty of Macros
Table of Contents
1. TLDR
2. Programs as Data, Data as Programs  emacs elisp lisp
3. What a Macro Actually Is  emacs elisp macros
4. You've Been Using Macros All Along  emacs elisp macros
5. Rolling Your Own  emacs elisp macros config
6. Strange Loops and Drawing Hands  emacs elisp hofstadter geb
7. Seeing Through the Magic  emacs elisp tooling
8. With Great Power  emacs elisp macros
9. Bending the Metal  emacs elisp lisp
1. TLDR
If you are an Emacs user with a keen eye, you will have noticed that in Emacs Lisp, code is data. After all, 'Lisp' is shorthand for 'List Processing'. One of Elisp's most beautiful features is the fortuitous blur between the thing that is processing the list (the program) and the list itself (the data). The macro in Elisp is a utility that exploits this blur and allows you to leverage this dualism between program and data in many useful and fascinating ways.
In this post, I want to swoon about macros, explain what "homoiconic" actually means, demonstrate their ubiquity in Elisp, depict their beauty on a detour through Hofstadter's strange loops and Escher's prints, and finally show off some tooling (macroexpand, emacs-lisp-macroexpand, macrostep) that enhances both comprehension and appreciation of macros.
Here is the Escher imagery we'll be leaning on along the way:
2. Programs as Data, Data as Programs  emacs elisp lisp
The kernel of Lisp has a crystalline purity that not only appeals to the esthetic sense, but also makes Lisp a far more flexible language than most others.
— Douglas Hofstadter
An important word for this post is homoiconic. A language is homoiconic when its programs are written in the language's own data structures.
Few languages are homoiconic, and the Lisp family wears the property most proudly, with Emacs Lisp (Elisp) being the dialect many of us are most familiar with. In Elisp, source code is lists, symbols, strings, and numbers. Code looks exactly the same as lists you build with cons and take apart with car and cdr.
The distinction between program and data is exhibited by a specific, special character, the glorious ':
;; a program: evaluates to 3<br>(+ 1 2)
;; data: a list of three elements — a symbol and two numbers<br>'(+ 1 2)
The quote tells the evaluator not to run the form that follows, but to treat it as plain data (a list).
To emphasize that program and data are equivalent in Elisp, running eval on the quoted list (as in (eval '(+ 1 2))) will turn it into a program, where the function is addition, and its arguments are the numbers 1 and 2.
That dualism lies at the heart of the language. Any piece of code is one character away from being a value you can inspect, transform, and rebuild; and any suitably-shaped value is one function call away from being a program.
So in Elisp, we say Program = Data, even though that's a little too simplistic, because we saw how correctly the Lisp interpreter deciphers when a list is being represented as a program versus when it is being represented as data…. The point is the list: that's the unifying form. Maybe more appropriately, we can say the program and data take the same form, or as previously mentioned, Elisp's programs are written in Elisp's own data structures.
This post was motivated by a simultaneous obsession with Douglas Hofstadter's writing and Elisp macros, so be prepared for many depictive metaphors from one of Hofstadter's favourite artists, M.C. Escher. Here's the first:
Escher drew this kind of dualism as a woodcut. The ants of Möbius Strip II appear to march on both sides of a strip. The image is provocative enough at first glance, but I invite you to follow any one of them around and discover that the two sides are one continuous surface. Program and data are the two sides of Elisp's homoiconic Möbius.
Figure 1: M.C. Escher, Möbius Strip II (1963). Two sides, one surface. © The M.C. Escher Company.
In most languages, metaprogramming lives in a separate layer with its own representation of code like templates, reflection APIs, token streams, quasi-quoted ASTs. Some of those layers are crude and some are genuinely sophisticated, but each is a wall between code and data. In Elisp there was never a wall to tunnel through. Elisp enables metaprogramming, but it's the same language, and the same data structures, all the way down.
3. What a Macro Actually Is  emacs elisp macros
Consider a regular function in Elisp. A function receives values and computes a value at runtime.
On the other hand, a macro receives code (the raw, unevaluated forms typed at its call site) and returns new code, which is then evaluated in its place. Macros run at...