Advanced Macros

23.1 Avoiding Variable Capture with gensym

;; BUG: captures the user's variable named TMP
(defmacro bad-swap (a b) `(let ((tmp ,a)) (setf ,a ,b ,b tmp)))

;; FIX: generate a unique symbol
(defmacro good-swap (a b)
  (let ((tmp (gensym "TMP")))
    `(let ((,tmp ,a)) (setf ,a ,b ,b ,tmp))))
;; (Standard CL already has ROTATEF for this.)

;; alexandria:with-gensyms simplifies this
(defmacro my-swap (a b)
  (alexandria:with-gensyms (tmp)
    `(let ((,tmp ,a)) (setf ,a ,b ,b ,tmp))))

23.2 Avoiding Multiple Evaluation: once-only

;; BUG: (square-it (incf x)) increments twice
(defmacro bad-square (x) `(* ,x ,x))

;; FIX: evaluate once into a gensym
(defmacro good-square (x)
  (let ((v (gensym)))
    `(let ((,v ,x)) (* ,v ,v))))

(defmacro square* (x)
  (alexandria:once-only (x)
    `(* ,x ,x)))

23.3 Anaphoric Macros (Intentional Capture)

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

(aif (gethash :key *table*)
     (format t "Found: ~a~%" it)
     (format t "Missing~%"))

(defmacro alambda (params &body body)     ; recursive lambda via SELF
  `(labels ((self ,params ,@body)) #'self))
(funcall (alambda (n) (if (<= n 1) 1 (* n (self (1- n))))) 5)   ; 120

23.4 Macro-Writing Tools and Techniques

macroexpand (fully expand the top form), macroexpand-1 (one step), SLIME's C-c C-m (expand at point), and C-c M-m (expand all).

&body vs &rest (indentation hints), &whole (the entire form), &environment (the lexical environment for nested expansion).

Compiler macros (define-compiler-macro): optimize calls to a function at compile time while keeping it a real function.

Symbol macros (define-symbol-macro, symbol-macrolet): symbols that expand into forms (used by with-slots).

macrolet: local macros.

Books: *On Lisp* (Paul Graham) and *Let Over Lambda* (Doug Hoyte) are the macro classics.

(macrolet ((double (x) `(* 2 ,x)))
  (double 21))                           ; 42

(symbol-macrolet ((first-item (car items)))
  (let ((items (list 1 2 3)))
    (setf first-item 100)
    items))                              ; (100 2 3)

(define-compiler-macro square (&whole form x)
  (if (numberp x) (* x x) form))         ; constant-fold (square 5) at compile time