Skip to content
Lesson 9 of 13

Step 1 of 5 · Reading · ~1 min

Read

Advanced Topics

Macros: programs that write programs

A macro looks like a function call but operates on unevaluated source code. It returns new code, which is then evaluated.

(defmacro unless (cond body)
   (list 'if cond '() body))

(unless (= 1 2) "ran")

Step by step:

  1. The macro unless is called with its arguments UNEVALUATED: the list (= 1 2) and the string "ran". Nothing has compared 1 to 2 yet.
  2. The body runs and returns [if, (= 1 2), (), "ran"] — the s-expression (if (= 1 2) () "ran"). That is code, not a value.
  3. The evaluator runs that code. (= 1 2) is false, so the if takes its ELSE branch and the result is "ran".

The argument order in the emitted if is the whole trick. unless puts the body in the else position, so it runs precisely when the condition is false — the opposite of when, which is the same macro with body and () swapped. Change (list 'if cond '() body) to (list 'if cond body '()) and you have written when instead.

Why macros matter

You can invent new syntax:

  • when / unless / cond (multi-way branch)
  • let (local bindings)
  • for (loops as a macro over recursion)
  • pattern matching
  • async/await
  • DSLs (HTML templates, SQL builders)

In most languages, control flow is fixed by the parser. In Lisp, every user can extend it.

Hygiene (advanced)

A "non-hygienic" macro like ours can accidentally clash with user variable names. Scheme's syntax-rules (R5RS) introduces hygienic macros that auto-rename to prevent capture. Common Lisp programmers use gensym manually. Worth reading about; out of scope here.

Up nextPutting It All TogetherAdvanced Topics

Discussion

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

Sign in to post a comment or reply.

Loading…