Macros

16.1 What Macros Are

A macro is a function that runs at compile/expansion time, takes unevaluated code (s-expressions) as input, and returns new code to be evaluated in its place.

Use macros to create new control structures, eliminate boilerplate, and build embedded languages. Functions can't do these things because their arguments are evaluated first.

16.2 Macros in Each Dialect

;; Common Lisp: defmacro with quasiquote
(defmacro my-unless (condition &body body)
  `(if ,condition nil (progn ,@body)))
(my-unless (> 1 2) (print "1 is not greater than 2"))
(macroexpand-1 '(my-unless (> 1 2) (print "x")))
;; => (IF (> 1 2) NIL (PROGN (PRINT "x")))

(defmacro while (test &body body)
  `(loop (unless ,test (return)) ,@body))
;; Scheme: hygienic pattern-based macros with syntax-rules
(define-syntax my-unless
  (syntax-rules ()
    ((_ condition body ...)
     (if condition #f (begin body ...)))))

(define-syntax swap!
  (syntax-rules ()
    ((_ a b) (let ((tmp a)) (set! a b) (set! b tmp)))))

(define x 1) (define y 2)
(swap! x y)                              ; x = 2, y = 1, even if a variable is named tmp (hygiene)

(define-syntax my-or
  (syntax-rules ()
    ((_) #f)
    ((_ e) e)
    ((_ e rest ...) (let ((t e)) (if t t (my-or rest ...))))))
;; Clojure: defmacro with syntax-quote (auto-namespaced symbols, # for gensyms)
(defmacro unless [condition & body]
  `(if ~condition nil (do ~@body)))

(defmacro with-timing [& body]
  `(let [start# (System/nanoTime)
         result# (do ~@body)]
     (println "Took" (/ (- (System/nanoTime) start#) 1e6) "ms")
     result#))
(macroexpand '(unless false (println "hi")))
;; Emacs Lisp
(defmacro my-inc (var) `(setq ,var (1+ ,var)))

16.3 Macro Pitfalls

Variable capture: a macro's internal variable names can collide with user variables. Use gensym (Common Lisp, Emacs Lisp), auto-gensyms name# (Clojure), or hygienic macros (Scheme, Racket).

Multiple evaluation: if a macro uses an argument twice, the argument's side effects happen twice. Bind it to a temporary first.

Don't use a macro when a function will do. Macros aren't first-class (you can't map a macro).