Macro Basics
22.1 Defining Macros
;; A macro receives UNEVALUATED forms and returns a new form
(defmacro my-when (condition &body body)
`(if ,condition (progn ,@body) nil))
(my-when (> 3 2)
(print "three")
(print "is bigger"))
(macroexpand-1 '(my-when (> 3 2) (print "x")))
;; => (IF (> 3 2) (PROGN (PRINT "x")) NIL), T
(defmacro while (test &body body)
`(loop while ,test do (progn ,@body)))
(let ((i 0)) (while (< i 3) (print i) (incf i)))
(defmacro unless* (test &body body) `(when (not ,test) ,@body))22.2 Backquote Refresher
(let ((x 1) (ys '(2 3)))
(list `(a x) ; (A X)
`(a ,x) ; (A 1)
`(a ,ys) ; (A (2 3))
`(a ,@ys) ; (A 2 3)
`(a ,@ys b))) ; (A 2 3 B)22.3 When to Write a Macro
To control evaluation (when, unless, short-circuiting), create new binding forms (with-open-file, with-timing), eliminate boilerplate (define-* forms), or build DSLs. Otherwise, write a function: functions are first-class, easier to debug, and composable.
22.4 Common Macro Patterns
;; with- macros: resource management
(defmacro with-timing (&body body)
(let ((start (gensym "START")))
`(let ((,start (get-internal-real-time)))
(multiple-value-prog1 (progn ,@body)
(format t "~&Elapsed: ~,3f s~%"
(/ (- (get-internal-real-time) ,start) internal-time-units-per-second))))))
(with-timing (sleep 0.1) :done)
;; define- macros: declarative definitions
(defmacro define-getter (name key)
`(defun ,name (plist) (getf plist ,key)))
(define-getter person-name :name)
(person-name '(:name "Ana")) ; "Ana"
;; Macros that generate multiple definitions
(defmacro define-predicates (&rest types)
`(progn
,@(loop for type in types
collect `(defun ,(intern (format nil "~a-P*" (symbol-name type))) (x)
(typep x ',type)))))
(define-predicates integer string) ; defines INTEGER-P* and STRING-P*