[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Using Macros in Lisp



More general comments on macros.

Careful use of macros makes source code much easier to read and because
of that your programs will be easier to debug.  For example, which of
the following is easier to read?

  (with-open-file (stream file :direction :output)
    (write-stuff-to-stream stream))

or

  (let ((stream (open file :direction :output))
        (status :abort))
    (unwind-protect
        (progn
          (process-file stream)
          (setf status :ok))
      (close stream :abort (not (eq status :ok)))))

This example also illustrates the primary correct use of macros, namely,
to define new syntax which captures a common control abstraction.  Other
examples of this are dotimes, dolist, cond, etc.

Similarly, macros are used to capture common programming idioms, like
push, pop, incf, shiftf, etc.  These could not be written without
macros, because you need to get the argument place forms, not just the
values.


The other major use of macros is creating top-level definition forms
like defun, defstruct, defclass, etc.  Again, which would you rather
read:


  (defclass a ()
    ((slot1 :initform nil))
    (:documentation "A class"))

or

  (define-class 'a
                'standard-class
                nil
                '((:name s1 :initfuncion #'(lambda () nil)))
                '((:documentation "A class")))


A popular misconception is that macros are used `for efficiency.' As
has been pointed out both here and in comp.lang.lisp, macros are not
functions and can not be used as functional arguments.  (E.g., you can't
apply a macro or use it as a :test argument to a sequence function.)

So, if you are worried about efficiency, don't write:

  (defmacro frame-name (x)
    `(car ,x))

instead write this:

  (proclaim (inline frame-name))
  (defun frame-name (x)
    (car x))

By writing a function you can then do things like:

  (find "Dog" list-of-frames :key #'frame-name :test #'string=)

  (mapcar #'frame-name list-of-frames)


Unfortunately, I don't know of any text that describes writing anything
other than trivial macros.  Probably, the best thing to do is to look at
code written by good lisp programmers. Important things to consider when
writing macros are:

  -- Take care with local variables introduced by the macro.
     (i.e., use gensyms for any new locals)

  -- Take care to preserve the left to right order of argument
     evaluation.

  -- Don't evaluate argument forms more than once.

  -- Know the difference between read time, macro expansion time,
     compile time, and load time.

  -- Don't do any side effects in your macro.  A macro is just a source
     to source transformation.

  -- Make sure the macroexpansion doesn't cause spurious compiler
     warnings.

In addition your macro should fit in with the language.  For example, if
you write a macro (WITH-FROB <body>) it should return the value of the
last form in body -- not the value of whatever cleanup forms you need to
do.

Macros are a powerful tool for writing clear, abstract, concise code --
but they are certainly not the only one.  When used correctly they help
you to concentrate on the problem rather than worrying about the
implementation details.  Knowing when macros are appropriate only comes
with experience.


(I hope this doesn't come off as preachy.)


Kevin Gallagher
Gallagher@cs.umass.edu