Declarative Languages
Lecture #11

Purpose: Evaluation rules and macros

11.1 Recall the evaluation rules for lists

We stated early on (section 3.1) that, to evaluate a list:

We are now in a position to return to this subject and cover it more precisely, recalling that there are two types of "not a function": macros and special operators. They share the properties that: They differ in that:  So why you should bother with macros?. The answer to this is that macros result in code which is more:


11.2 Review of special operators

Let's start with the special operators. There are 25 of these in the language and there is no way to add more of your own. The function special-operator-p called with one of these symbols returns true:
    (special-operator-p 'setq)  =>  true
    (special-operator-p 'setf)  =>  nil

We have met the following 10 special operators so far:

The following 13 (of which the first 5 will be more useful to you over the years than the rest) are outside the scope of this course: That leaves two more which you might get by without ever using, but for completeness I do need to mention them here briefly: 11.3 Back to macros (see also section 7.3)

In the course of the first ten lectures we have met a fair number of lisp's 91 macros:

and  decf  defconstant  defparameter  defstruct  defun  dolist  dotimes  ignore-errors  incf  lambda  loop  or  pop  prog1  prog2  push  pushnew  return  setf  trace  untrace  with-open-file
Some are easily defined (e.g. and, return), others are more complex in their behaviour (the defining macros, for instance), and several (push) look OK from a distance but turn out to be slightly more fiendish when you get close up. Today we will Let's start with a very simple example: the macro return. We recall that this has syntax
    (return &optional what)
and that it is shorthand for / equivalent to invoking the special operator return-from:
    (return what) ==  (return-from nil what)

How is this equivalence managed?

Lisp provides the following assistance:

So:
(macro-function 'return)            =>  true
(macroexpand-1 '(return))           =>  (return-from nil)
(macroexpand-1 '(return (foo bar))  =>  (return-from nil (foo bar))
The way you define this behaviour is via yet another (defining) macro: defmacro. For instance:
(defmacro return (&optional what)
  (list 'return-from nil what))
To see how this works in practice, suppose we are running the following code:
(let ((count 0))
  (dolist (element list)
    (if (eq element thing)
        (return count))
    (incf count)))
and that we have arrived at the return form. The lisp system determines (by calling special-operator-p) that return is not a special operator. It then determines (by calling macro-function) that return is a macro. The function macroexpand-1 is then called with the entire lisp form constituting the macro call as its argument:
    (macroexpand-1 '(return count))
That function invokes the macro definition for return, which generates a list with three elements: the symbols return-from, nil and count. It has therefore effectively generated a new lisp form:
    (return-from nil count)
and this is evaluated in place of the original form.

You don't have to think all this through every time though. You can just say: it's a macro, so the form will be replaced by whatever the macro function expands it into.

Macros transform lisp source into new code. To quote Graham:

You define a macro by saying what a call should expand into.
Underneath, macros are just functions that return expressions.

Macros are an incredibly flexible and powerful way to expand the language. They beat #define into a cocked hat. Effectively, macros are programs which write more programs. They permit code which is compact, readable, maintainable. And because macros use lisp syntax to transform lisp into more lisp, you don't have to "leave the language" to use them.

11.4 Conditionals: when, unless and cond

Another simple macro is when. This has syntax
    (when test-form form-1 form-2 ... form-n)
which is equivalent to
    (if test-form (progn form-1 form-2 ... form-n))
In other words, when evaluates its first form. If that is non-nil, it evaluates all its remaining forms as an implicit progn and returns the value of the final form, otherwise when simply returns nil.

So

(if (char= next #\;)
    (progn
      (setf previous-position position)
      (incf count)))
can be rewritten
(when (char= next #\;)
  (setf previous-position position)
  (incf count))
We can code its expander thus:
(defmacro when (test-form &rest forms)
  (list 'if
        test-form
        (cons 'progn forms)))
Note that this is getting a little difficult to read (and to code correctly!) - you have to stop and think about when to use list and when to use cons, and this is still a fairly simple example. The trick is to use the backquote notation (section 4.5):
(defmacro when (test-form &rest forms)
  `(if ,test-form
       (progn ,@forms)))
Macro writing now becomes a whole load easier, because you simply write out the form you're trying to generate and place Another example: the macro unless which is like when but only runs the forms if the test-form evaluated to nil. The following would both work as macro definitions for unless. Note that one of them defines unless in terms of another macro (when), so that a call involving unless would have to be macroexpanded twice.
(defmacro unless (test-form &rest forms)
  `(if ,test-form
       nil
     (progn ,@forms)))

(defmacro unless (test-form &rest forms)
  `(when (not ,test-form)
     ,@forms))

Finally in this section, the macro cond removes the need for vast nested chains of if statements. The syntax is:
    (cond clause-1 clause-2 ... clause-n)
where each clause looks like this:
    (test-form form-1 ... form-n)
If the test in clause-1 is true then all the subforms in that clause are evaluated (implicit progn) and the value of the last form returned. Otherwise the next clause is tried, and so on in order until either one of the tests succeeds or they all fail.

This definition suggests a recursive implementation:

(defmacro cond (&rest clauses)
  (when clauses
    (let* ((this (first clauses))
           (others (rest clauses))
           (test (first this))
           (forms (rest this)))
      `(if ,test
           (progn ,@forms)
         (cond ,@others)))))
but an iterative one will do too:
(defmacro cond (&rest clauses)
  (let ((expansion nil))
    (dolist (this (reverse clauses))
      (let ((test (first this))
            (forms (rest this)))
        (setf expansion
              `(if ,test
                   (progn ,@forms)
                 ,expansion))))
    expansion))
Using the first expansion,
(cond (foo (bar) baz)
      (wombat wibble)
      (t 99))
expands to
(if foo
    (progn (bar) baz)
  (cond (baz wibble)
        (t 99)))
using the second we get
(if foo
    (progn (bar) baz)
  (if wombat
      (progn wibble)
    (if t
        (progn 99)
      nil)))
Assuming I haven't made any mistakes in this, the forms are equivalent and either macro definition will do.

As you can see, writing macros can very rapidly become a complex task. However, the results tend to be rewarding, because the language is so much more powerful for the presence of macros.

11.5 Further examples of macros

11.6 Revisiting the function evaluation rules

 So, to evaluate a non empty list (seeing as the empty list is identical to the self-evaluating symbol nil), we look at the first element of that list.

  1. If it is a symbol which names one of the 25 special operators, carry out the appropriate behaviour for that special operator.
  2. Otherwise, if it is a symbol which has a macro definition, expand that macro and restart the evaluation (recursive loop!)
  3. Otherwise, we must have a function. This may be either a symbol which is fboundp, or a list of the form
  4. We evaluate each argument in turn (from "left to right") and call the function with the results of these evaluations. The result of the evaluation is whatever value the function returns. Example of this rather arcane use of lambda: but note that we could have done the same thing more legibly either by inserting a funcall
      (funcall (lambda (key value)
                 (setf (gethash key *table*) value))
               (first pair)
               (second pair))
    or [much better] by transforming the expression to use let* and so you rarely see lambda used like this.


11.7 Practical session / Suggested activity

11.8 Further reading / exercises.
Copyright (C) Nick Levine 1999. All rights reserved.
Last modified 2000-09-14
$Id: //info.ravenbrook.com/user/ndl/lisp/declarative/lectures/lectures/lecture-11.html#2 $