Skip to content
Macros in Depth
step 1/5

Reading — step 1 of 5

Learn

~2 min readMacros and Special Forms

You've seen basic macros. The deeper power: code that writes code.

Hygiene with gensym — generate unique symbols to avoid name capture:

(defmacro swap (a b)
    (let ((tmp (gensym "TMP")))
        `(let ((,tmp ,a))
            (setf ,a ,b)
            (setf ,b ,tmp))))

(let ((x 1) (y 2))
    (swap x y)
    (format t "~a ~a~%" x y))

Without gensym, if the user happened to bind tmp in their code, the macro would shadow it.

Recursive expansion:

(defmacro repeat (n &body body)
    `(dotimes (i ,n) ,@body))

(defmacro repeat-with-index (n &body body)
    `(dotimes (i ,n)
        (let ((index i))
            ,@body)))

The second one binds index for the body — convention for what i represents.

Compile-time computation:

(defmacro precompute-table (size)
    (let ((table (make-array size)))
        (dotimes (i size)
            (setf (aref table i) (* i i)))
        `(quote ,table)))

(defparameter *squares* (precompute-table 100))

The table is built when the macro expands, baked into the program. No runtime computation.

Macros that build OTHER macros:

(defmacro define-status-checker (name status)
    `(defmacro ,name (obj)
        `(eq (slot-value ,obj 'state) ',,status)))

(define-status-checker active? :active)
(define-status-checker pending? :pending)

Anaphoric macros — capture a name implicitly:

(defmacro aif (test then &optional else)
    `(let ((it ,test))
        (if it ,then ,else)))

(aif (find-user id)
    (format t "got ~a" it)         ;; "it" is bound to the result
    (format t "not found"))

The it variable is intentionally captured ("anaphoric" = referring back). Useful but trips up readers — Common Lisp purists usually prefer explicit variables.

Macros vs functions: macros run at compile time, functions at runtime. Use a function whenever possible — macros are harder to debug, can't be passed as values, and compose worse.

Useful macros to know:

  • loop — the iteration mini-language
  • with-open-file, with-output-to-string — resource management
  • defstruct, defclass — class definitions ARE macros
  • setf — universal mutator (knows how to write to any "place")

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…