Gödel, Escher, Elisp: The Beauty of Macros

JNRowe1 pts0 comments

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 &nbspemacs elisp lisp

3. What a Macro Actually Is &nbspemacs elisp macros

4. You've Been Using Macros All Along &nbspemacs elisp macros

5. Rolling Your Own &nbspemacs elisp macros config

6. Strange Loops and Drawing Hands &nbspemacs elisp hofstadter geb

7. Seeing Through the Magic &nbspemacs elisp tooling

8. With Great Power &nbspemacs elisp macros

9. Bending the Metal &nbspemacs 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 &nbspemacs 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 &nbspemacs 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...

elisp data macros escher lisp program

Related Articles