Lisp Macros
Macros are for transformation. Functions are for computation.
A first example
In Lisp we have an if expression.
(bind ((x 3)) (if (>= x 0) 'non-negative 'negative))
We also have a cond expression.
(bind ((x 3)) (cond ((> x 0) 'positive) ((< x 0) 'negative) ((= x 0) 'zero)))
But what if Lisp didn't have a cond expression? Let's write our own kond
expression, as a Lisp macro.
(defmacro kond (&rest clauses) (if (null clauses) nil (let ((clause (first clauses)) (rest (rest clauses))) (if (eq (first clause) 't) `(progn ,@(rest clause)) `(if ,(first clause) (progn ,@(rest clause)) (kond ,@rest))))))
Let's first check that it works.
(bind ((x 3)) (kond ((> x 0) 'positive) ((< x 0) 'negative) ((= x 0) 'zero)))
What is happening here?
First let's try kond with no clauses
(kond)
That gives us nil. We can see how that happens from the first two lines of the
definition of kond
(defmacro kond (&rest clauses)(if (null clauses) ;nil ;(let ((clause (first clauses)) (rest (rest clauses))) (if (eq (first clause) 't) `(progn ,@(rest clause)) `(if ,(first clause) (progn ,@(rest clause)) (kond ,@rest))))))
Now let's call kond with an otherwise clause, using t.
(kond (t 'default))
How does this work in the definition of kond?
(defmacro kond (&rest clauses) (if (null clauses) nil(let ((clause (first clauses)) ;(rest (rest clauses))) ;(if (eq (first clause) 't) ;`(progn ,@(rest clause)) ;`(if ,(first clause) (progn ,@(rest clause)) (kond ,@rest))))))
We see that
- the else clause of the
ifis triggered, clausesis broken up into itsfirstandrest,- the variable
clauseis bound to(t 'default) - the then clause of the second
ifis triggered, and `(progn ,@(rest clause))is returned.
When we fill in that template we see
clauseis bound to(t 'default)- so
(rest clause)is'(default) - which is spliced in to yield
'default - which is evaluated to give
'default
Finally let's call kond with three clauses.
(bind ((x 3)) (kond ((> x 0) 'positive) ((< x 0) 'negative) ((= x 0) 'zero)))
In the definition, this triggers these lines.
(defmacro kond (&rest clauses) (if (null clauses) nil(let ((clause (first clauses)) ;(rest (rest clauses))) ;(if (eq (first clause) 't) `(progn ,@(rest clause))`(if ,(first clause) ;(progn ,@(rest clause)) ;(kond ,@rest)))))) ;
The following forms have the corresponding values.
clausesis'(((> x 0) 'positive) ((< x 0) 'negative) ((= x 0) 'zero))clauseis(first clauses)which is((> x 0) 'positive)restis(rest clauses)which is'(((< x 0) 'negative) ((= x 0) 'zero))(first clause)is(> x 0)(rest clause)is'(positive)
So now we can put them all together into this template
`(if ,(first clause) (progn ,@(rest clause)) (kond ,@rest))
to get
(if (> x 0) 'positive (kond ((< x 0) 'negative) ((= x 0) 'zero)))
We see that there is a recursive call to kond so we continue transforming that
call. It will lead to two more recursive calls to kond finally resulting in:
(if (> x 0) (progn 'positive) (if (< x 0) (progn 'negative) (if (= x 0) (progn 'zero) nil)))
And this, of course, is precisely what we want kond to do. The transformations
specified for the kond macro are all carried out before any computation is
done. Only when we have the fully expanded form, does the computation begin.
Using SLIME in Emacs, you can see these expansions in action. Put the cursor on
the name of the macro (kond) at a call location and press C-c M-e to enter
macrostep-mode. Now every time you press e it will expand further. Press q
when you are done to exit macrostep-mode.
Macros
Macro evaluation is a two-step process
- An s-expression is built, usually by filling in a template written with
backquote (
`) - The s-expression is evaluated.
Lisp already has a one-legged if called when. It works like this.
(bind ((x 4)) (when (> x 0) 'positive))
We can see that it expands into if using macrostep-mode
(bind ((x 4)) (IF (> X 0) 'POSITIVE))
So the only reason for the existence of when is to indicate that we do indeed
want the conditional to be one-legged.
Let's write a macro for our own version of when which we will call whenever. It
looks like this.
(defmacro whenever (test form) `(if ,test ,form))
We see that it is a template which drops the test and form into an if.
What happens when we make the following call?
(bind ((x 4)) (whenever (> x 0) 'positive))
- The Lisp interpreter sees that
whateveris a macro and gets its template`(if ,test ,form) - It drops
testandforminto the template as specified without evaluating them(if (> x 0) 'positive). This is called macro expansion. - It evaluates the resulting form.
Sometimes further expansion is required when the macro expansion yields a form
which itself contains macros, such as with kond. This expansion continues until
a form is reached which no longer contains any macro call. That resulting form
is then evaluated.