macros: code which writes code

Macros provide a powerful metaprogramming capability to Lisp - the ability to write code which writes code. Because the internal representation of code, the abstract syntax tree, is the list, code generators are little more than functions which return lists. Code generation occurs before compilation, so macro forms, like function and special forms, result in efficient machine code.

Macros work by substituting one form with another before the value is calculated. Amazingly, no special syntax is required. This process, called macro-expansion, is part of the normal evaluation procedure. Symbols and lists (but not self-evaluating forms) may be used as macros:

symbol =M=> form => value
(operator ...args...) =M=> form => value

Note that if the form produced by macro expansion is itself a macro form, it will also be subject to macro expansion. This is repeated until a non-macro form is computed.

symbol macros Symbols are defined as macro forms using DEFINE-SYMBOL-MACRO or introduced in a local block using SYMBOL-MACROLET.

(DEFINE-SYMBOL-MACRO symbol form)

DEFINE-SYMBOL-MACRO specifies that symbol will be replaced with form wherever symbol appears in an evaluation context. SYMBOL-MACROLET introduces symbol macros within a local block of code:

(SYMBOL-MACROLET ((symbol form)*)
  ...code...)

Notice the similarity to DEFVAR/DEFPARAMETER and LET. One provides a global definition, the other provides a local definition.

So far, macros aren't very interesting. The real power of macros becomes apparent when compound macros are considered

compound macro forms Compound macro forms are lists where the operator names a macro. Macro operators are defined using DEFMACRO - which is itself a macro, amazingly! Under the hood, DEFMACRO creates a function, called the macro-function, which is used by the interpreter to process lists which beginning with the macro operator. Typically, the function computes a new list, though it could return the same list or a symbol or some other form. Just as for symbol macros, the object computed by the macro function is treated as code to be evaluated.

when Let us turn to an example. A common programming idiom is to execute a block of code when some condition is met. Let us call this operation "WHEN", and imagine it will be used in the following way:

(WHEN test
  form1
  form2
  ...)

This requires branching logic and serial execution of a list of forms. Fortunately, Lisp provides two special operators which do the necessary: IF and PROGN. We could write:

(IF test (PROGN form1 form2 ...))

How can we write WHEN using IF and PRGN? Defining WHEN as a function will not work since

(when test form1 form2 form3...)

would always evaluate the forms whether or not the test was true. What we want to do is substitute the code which performs the test (not the result of the test) into the IF/PROGN form above. This is exactly what macros are for.

DEFMACRO Let's define WHEN using DEFMACRO and backquote notation (see the sidebar) to generate the IF/PROGN list above. In fact, WHEN is defined as a macro by the Common Lisp spec. You should be able to imagine

(defmacro when (test &body forms)
  `(if ,test (progn ,@forms)))

This macro substitutes whatever appears at test and forms into the list template described using the backquote (`) syntax. The &body lambda list keyword collects all the remaining arguments into a list. It is functionally identical to &rest, but is used in macros to indicate that the arguments represent code. The &body keyword is only used by code editors as a hint to format the text as code. Otherwise, &rest and &body are interchangeable. The comma-at syntax splices the list of forms into the PROGN list.

the macroexpand function The effect of this is to produce a macro-function computes the corresponding IF/PROGN code given a WHEN form. You can see this macro-function at work by using the Lisp function, MACROEXPAND. This is a function which takes a form as an argument. If the form is a macro form, it expands it, and returns it as the first value. This function also returns a second value which indicates whether the argument was a macro form:

B-USER 1> (macroexpand '(when (mytest) (do-a) (do-b) (do-c)))
(IF (MYTEST) (PROGN (DO-A) (DO-B) (DO-C)))
T

Macroexpand looks up the macro-function which was created by DEFMACRO and passes the list (WHEN (MYTEST) (DO-A)(DO-B) (DO-C)) to it. This macro function has code which parses the list, and creates bindings for the parameters, TEST and FORMS. TEST will be bound to (MYTEST) and FORMS will be bound to ((DO-A) (DO-B) (DO-C)) when the backquote form, `(IF ,TEST (PROGN ,@FORMS)) is evaluated.

Macroexpand is called internally during EVAL. The argument is the form generated by the just-completed READ step. Thus the arguments of a macro are the very objects produced by the reader. Another useful function is MACROEXPAND-1, which only performs one step of macroexpansion. This is useful when you are debugging a macro which expands to another macro.

macro expansion time Macros are expanded before regular function evaluation occurs. Just like reading happens before evaluation ("read time is before eval time"), we say that macro expansion time is before function evaluation time. In fact, macros are expanded during function definition. As a result, a macro form is expanded once, though the code it expands to may be executed many times.

For instance let's use WHEN inside a function. REPORT-IF-NUMBER prints a message if its argument is a number:

B-USER 1> (defun report-if-number (x)
            (when (floatp x) (princ "FLOATING-POINT "))
            (when (numberp x) (princ "NUMBER!"))
            x)))
; when is expanded thrice here
REPORT-IF-NUMBER

B-USER 2> (report-if-number 1) ; macro does NOT expand here
NUMBER!
1

B-USER 3> (report-if-number 1.5) ; or here
FLOATING-POINT NUMBER!
1.5
B-USER 4> (report-if-number "abc") ; or here
"abc"

Note that WHEN is expanded twice - at B-USER 1. Calling REPORT-IF-NUMBER on the following lines causes the IF/PROGN forms computed by WHEN to be evaluated.

local macros The MACROLET special form is the "local" version of DEFMACRO, much like LET, FLET, SYMBOL-MACROLET are local versions of DEFVAR, DEFUN, and DEFINE-SYMBOL-MACRO. This special operator allows you to define a macro with lexical scope.:

(macrolet ((local-when (test &body forms)
             `(if ,test (progn ,@forms))))
  (local-when (test-something)
    (do-a)
    (do-b)
    ...)

how to build OR As another example, let's define our own version of the logical test, OR. OR should be true (ie., non-NIL) if any of its arguments are true. It may be defined in terms of a series of nested IF tests: given arguments A1...An, if A1 is true, the expression is true, if not, if A2 is true, the expression is true, if not... and so on; if there are no arguments, the expression is false (NIL).

We define OR logic below by defining it in terms of the IF special operator. Remember, the IF is written (IF test then else), where else is optional. The then form is evaluated only if test is true; the else form only when test is false. Here, we define NEW-OR, and test it out:

B-USER 1> (defmacro new-or (&rest args)
           (if args
             `(if ,(first args)
                 t
                (new-or ,@(rest args)))))

NEW-OR

B-USER 2> (new-or (> 1 2) (> 2 1))
T

B-USER 3> (new-or (> 1 2) (> 2 3))
NIL

B-USER 4> (macroexpand '(new-or (> 1 2) (> 2 1)))
(IF (> 1 2) T (NEW-OR (> 2 1)))
T

Again, the NEW-OR macro is doing is nothing more than computing a list. First, it checks whether any arguments have been provided. If not, NIL is returned. I.e., (NEW-OR)=M=> NIL => NIL. If arguments are provided, however, NEW-OR computes an IF form which tests the first argument.

(IF arg1 T (NEW-OR arg2 arg3 ...)
If this test succeeds, the IF form will return T, the constant which is used to represent "true" (though any non-NIL form would do). If it doesn't succeed, then it evaluates another NEW-OR form with the rest of the arguments. Thus, NEW-OR evaluates its arguments from left to right until a non-NIL result is encountered. It then returns T. The last test looks like this:
(IF last-arg T (NEW-OR)

At which point no more macroexpansion is necessary, since (NEW-OR)=M=>NIL.

Common Lisp's OR is defined very much like this. The main difference is that it stores the result of the test, and returns this when it is non-nil. For instance, whereas (NEW-OR NIL 3)=>T, (OR NIL 3)=>3.

This process, called macro expansion, happens automatically in the evaluator when a macro form is evaluated. However, you can force a single step of macro expansion (without evaluation) using the MACROEXPAND-1 function. This function takes a single argument and returns two values, the expanded form, and a generalized boolean (i.e., NIL or T) indicating whether any macro expansion occurred. This function is very useful for debugging macros. In the interpreter, the MACROEXPAND function calls MACROEXPAND-1 until the second value is NIL, indicating the macro expansion process is complete.

uses of macros Much of Common Lisp is implemented as macros. Even "primitive" operations - like defining a function, or logic operations like AND and OR, as we've seen - are defined as macros. As you gain familiarity with them, you will understand how to much of Common Lisp itself was written. In fact, many of the core forms provided by little b are implemented using macros. Remarkably, even DEFMACRO is defined as a macro.

Macros make it possible to describe new language features and even whole languages simply and consisely in Lisp by transforming a list into Lisp code. The powerful LOOP macro is a good example of just how far you can go with macros. Many computer scientists use Lisp for exactly this reason. Defining extensions to other languages requires a clumsy and brittle preprocessor step or tinkering with internals of the compiler/interpreter itself (if you have the source code), something not for the faint of heart.

In Lisp entire new sublanguages can be written solely with a combination of macros, functions and special operators. Reader macros, user-provided reader functions, complete the picture by allowing specialized notation to be developed. These combined capabilities make Lisp unequaled in the world of computing, and it is for this reason that it is called the mother of all languages. The powerful macro facility deserves a whole chapter to appreciate all the techniques that are made possible.6

creating objects