Macro Examples

Suppose we want to replace MAPCAR with a bit more elegant tool that doesn't use the antiquated name and doesn't require us to function-quote the first argument. Instead of

(MAPCAR #'+ '(1 2 3) '(2 3 4)
we want to write
(mapfirst + '(1 2 3) '(2 3 4)
and have the MAPCAR call constructed from this expression. This is the sort of task for which macros are designed. Macros are like functions in that they have arguments and a body which computes a result from those arguments, but macros leave their arguments unevaluated so that a new form -- the macro expansion -- can be computed from the arguments. The answer returned by a macro is the result of evaluating this new form.

Thus the execution of a macro has two phases:

  1. expansion of the body of the macro definition
  2. evaluation of the expansion using EVAL

To start working out mapfirst, let's just construct a form in which MAPCAR is prefixed to mapfirst's arguments:

>(DEFMACRO mapfirst (&REST args)(CONS 'MAPCAR args))
MAPFIRST

>(MACROEXPAND '(mapfirst #'+ '(1 2 3) '(2 3 4)))
	; this just lets us see what the macro has constructed from its argument.

(MAPCAR #'+ '(1 2 3) '(2 3 4))
T   ; this means that the argument to MACROEXPAND began with a macro name. 

>(mapfirst #'+ '(1 2 3) '(2 3 4))
(3 5 7)

Now we try to avoid quoting the function name by having the macro do it:

First attempt:

>(DEFMACRO mapfirst (&REST args)
  (CONS 'MAPCAR (CONS '#' args)))
MAPFIRST

>(MACROEXPAND '(mapfirst + '(1 2) '(3 4)) )

*** - EVAL: too few arguments given to CONS: (CONS '#'ARGS)

There's a problem with trying to quote the quote. Let's sidestep that:

Second try:

>(DEFMACRO mapfirst (fun &REST args)
  (CONS 'MAPCAR (CONS '(FUNCTION fun) args)))
MAPFIRST

>(MACROEXPAND '(mapfirst + '(1 2) '(3 4)) )
(MAPCAR #'FUN '(1 2) '(3 4))
T

Oh, dear! The parameter name "function" got quoted, not its value "+".

Once more:

>(DEFMACRO mapfirst (fun &REST args)
(CONS 'MAPCAR (CONS (LIST 'FUNCTION fun) args)))
MAPFIRST

>(MACROEXPAND '(mapfirst + '(1 2) '(3 4)) )
(MAPCAR #'+ '(1 2) '(3 4))
T

>(mapfirst + '(1 2) '(3 4))
(4 6)  

Making a macro template

In the previous example, the macroexpansion was specified using CONS and LIST; that is, the form was explicitly constructed using Lisp functions. It is usually easier to use substitution into a template, making use Lisp's back-quote and comma operators. (See "Template Construction".)

We might try rewriting our previous example as:

>(DEFMACRO mapfirst (function-name &REST args) `(MAPCAR (FUNCTION ,fun) ,args))
MAPFIRST

>(MACROEXPAND '(mapfirst + '( 1 2) '(3 4)))
(MAPCAR #'+ ('(1 2) '(3 4)))
T

This isn't quite right -- we don't want the &REST list of args here; we want to have the elements of the &REST list spliced in. Yet another macro operator comes to our rescue: the splice-comma ,@:

>(DEFMACRO mapfirst (fun &REST args) `(MAPCAR (FUNCTION ,fun) ,@args)))
MAPFIRST

>(MACROEXPAND '(mapfirst + '( 1 2) '(3 4)))
(MAPCAR #'+ '(1 2) '(3 4))


Author: Peter Roosen-Runge
Last Updated: October 6, 2005