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*