logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·01.11.26
← Back to news

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

Original article
chiply.dev|45 points|14 comments|by JNRowe|Aug 4, 2026

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

TL;DR: For the observant Emacs user, the magic of Emacs Lisp (Elisp) lies in the fact that code is data. This blur between the processor (the program) and the processed (the data) is the foundation of the macro, a tool that allows for fascinating metaprogramming.

This exploration delves into the concept of homoiconicity, the influence of Douglas Hofstadter and M.C. Escher, and the practical utility of macros—supported by tools like macroexpand, emacs-lisp-macroexpand, and macrostep.

![Escher Imagery Placeholder: A visual representation of recursive loops and dualities]


Programs as Data, Data as Programs

As Douglas Hofstadter noted, the "crystalline purity" of the Lisp kernel provides an aesthetic appeal and a level of flexibility rarely found in other languages. Central to this is the concept of homoiconicity.

Definition: A language is homoiconic when its programs are constructed using the language's own internal data structures.

In Elisp, the building blocks of code—symbols, strings, numbers, and lists—are the exact same structures used for data. Code is essentially a list that you can manipulate using car and cdr or build using cons.

The Role of the Quote

The distinction between a "program" and "data" is managed by a single, powerful character: the quote '.

ExpressionTypeResult/Interpretation
(+ 1 2)ProgramEvaluates to 3
'(+ 1 2)DataA list containing a symbol and two integers

By using (eval '(+ 1 2)), we can transform a piece of data back into an executable program. This implies a fundamental duality:

Program≈Data\text{Program} \approx \text{Data}Program≈Data

While saying they are identical is a slight oversimplification (since the interpreter must still distinguish between the two), it is accurate to say they share the same form.

The Möbius Strip of Metaprogramming

To visualize this, we can look to M.C. Escher's Möbius Strip II. In the woodcut, ants march on what appear to be two sides of a strip, only to realize they are on one continuous surface.

In most programming languages, metaprogramming is isolated separated by a wall. You might use:

  • Templates
  • Reflection APIs
  • Token streams
  • Quasi-quoted ASTs

In Elisp, there is no wall. Metaprogramming uses the same language and the same structures all the way down.


What a Macro Actually Is

To understand a macro, we must first contrast it with a standard function.

The Function Workflow:

  1. Evaluate all arguments.
  2. Pass the results to the function.
  3. Execute the function body.

The Macro Workflow:

  1. Receive the raw, unevaluated code (the forms) from the call site.
  2. Return a new piece of code.
  3. The evaluator then runs that new code in place of the macro call.

Essentially, a macro is a program that writes other programs. The only limit to what a macro can project is the limit of your imagination (and the capabilities of Elisp).

The Macro Toolkit

To build these "program-generating programs," Elisp provides quasiquotation:

  • ` (Backquote): Creates a code template.
  • , (Comma): Inserts a computed value into the template.
  • ,@ (Splicing): Inserts the elements of a list into the template.

Example: Reimagining unless

Here is a minimal implementation of the unless construct:

(defmacro my-unless (condition rest body)
  "Run BODY unless CONDITION is non-evaluated."
  (declare (indent 1))
  `(if ,condition 
       nil 
       ,@body))

If we use macroexpand-1 to see how this transforms, we get:

The Call:

(macroexpand-1 '(my-unless (file-exists-p "~/notes")
                           (make-directory "~/notes")
                           (message "created it")))

The Expansion:

(if (file-exists-p "~/notes") 
    nil 
    (make-directory "~/notes") 
    (message "created it"))

Why not just use a function?

You might wonder why my-unless cannot be a regular function. The answer lies in eager evaluation.

If my-unless were a function, Elisp would evaluate (make-directory "~/notes") before the function ever started. If the directory already existed, the program would crash with a file-already-exists error before the condition could even be checked.

By using a macro, the body remains inert data until the macro decides whether or not it should actually be executed. Control flow itself becomes a manipulatable object.