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) ; 12023.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