common-lisp Common Macro Patterns


Example

TODO: Maybe move the explanations to remarks and add examples separately

FOOF

In Common Lisp, there is a concept of Generalized References. They allow a programmer to setf values to various "places" as if they were variables. Macros that make use of this ability often have a F-postfix in the name. The place is usually the first argument to the macro.

Examples from the standard: INCF, DECF, ROTATEF, SHIFTF, REMF.

A silly example, a macro that flips the sign of a number store in a place:

(defmacro flipf (place)
  `(setf ,place (- ,place)))

WITH-FOO

Macros that acquire and safely release a resource are usually named with a WITH--prefix. The macro should usually use syntax like:

(with-foo (variable details-of-the-foo...)
  body...)

Examples from the standard: WITH-OPEN-FILE, WITH-OPEN-STREAM, WITH-INPUT-FROM-STRING, WITH-OUTPUT-TO-STRING.

One approach to implementing this type of macro that can avoid some of the pitfalls of name pollution and unintended multiple evaluation is by implementing a functional version first. For instance, the first step in implementing a with-widget macro that safely creates a widget and cleans up afterward might be a function:

(defun call-with-widget (args function)
  (let ((widget (apply #'make-widget args))) ; obtain WIDGET
    (unwind-protect (funcall function widget)  ; call FUNCTION with WIDGET
      (cleanup widget)                         ; cleanup

Because this is a function, there are no concerns about the scope of names within function or supplier, and it makes it easy to write a corresponding macro:

(defmacro with-widget ((var &rest args) &body body)
  `(call-with-widget (list ,@args) (lambda (,var) ,@body)))

DO-FOO

Macros that iterate over something are often named with a DO-prefix. The macro syntax should usually be in form

(do-foo (variable the-foo-being-done return-value)
  body...)

Examples from the standard: DOTIMES, DOLIST, DO-SYMBOLS.

FOOCASE, EFOOCASE, CFOOCASE

Macros that match an input against certain cases are often named with a CASE-postfix. There is often a E...CASE-variant, which signals an error if the input doesn't match any of the cases, and C...CASE, which signals a continuable error. They should have syntax like

(foocase input
  (case-to-match-against (optionally-some-params-for-the-case)
   case-body-forms...)
  more-cases...
  [(otherwise otherwise-body)])

Examples from the standard: CASE, TYPECASE, HANDLER-CASE.

For example, a macro that matches a string against regular expressions and binds the register groups to variables. Uses CL-PPCRE for regular expressions.

(defmacro regexcase (input &body cases)
  (let ((block-sym (gensym "block"))
        (input-sym (gensym "input")))
    `(let ((,input-sym ,input))
       (block ,block-sym
         ,@(loop for (regex vars . body) in cases
                 if (eql regex 'otherwise)
                   collect `(return-from ,block-sym (progn ,vars ,@body))
                 else
                   collect `(cl-ppcre:register-groups-bind ,vars
                                (,regex ,input-sym)
                              (return-from ,block-sym
                                (progn ,@body))))))))

(defun test (input)
  (regexcase input
    ("(\\d+)-(\\d+)" (foo bar)
      (format t "Foo: ~a, Bar: ~a~%" foo bar))
    ("Foo: (\\w+)$" (foo)
      (format t "Foo: ~a.~%" foo))
    (otherwise (format t "Didn't match.~%"))))

(test "asd 23-234 qwe")
; Foo: 23, Bar: 234
(test "Foo: Foobar")
; Foo: Foobar.
(test "Foo: 43 - 23")
; Didn't match.

DEFINE-FOO, DEFFOO

Macros that define things are usually named either with DEFINE- or DEF -prefix.

Examples from the standard: DEFUN, DEFMACRO, DEFINE-CONDITION.