TODO: Maybe move the explanations to remarks and add examples separately
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)))
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)))
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
.
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.
Macros that define things are usually named either with DEFINE-
or DEF
-prefix.
Examples from the standard: DEFUN
, DEFMACRO
, DEFINE-CONDITION
.