Search code examples
macroscommon-lispccl

Why does this common lisp macro not evaluate the first s-exp?


I studied define-easy-handler macro from the hunchentoot package (which creates a function with the name NAME), and got the defun part to work, but I can't get this macro to push NAME to a list called *observers*:

(defmacro add-observer (name params &body body)
  ;; add NAME to the list *observers*
  `(push ,name *observers*)
  ;; define a lisp function with the name NAME
  ;; with key arguments given in PARAMS
  `(defun ,name (&key ,@(loop for p in params
                                collect p))
       ,@body)))

An example call to #'add-observer is:

(add-observer below-80 (id blood-sugar)
              (when (< blood-sugar 80)
                (format t "Patient No: ~A is hypoglycemic." id))

The function NAME is defined and works fine, but the NAME is not added to the list *observers*. It does not matter if I put both s-expressions inside a progn or not. The macroexpand clearly shows the absence of the call to push with and without a progn. What am I interpreting wrong?

EDIT

When I try this with a progn like:

`(progn
  (push ...
  (defn ...

it fails with Unbound variable: below-80. And when I put the backquotes back to the #'push and #'defun, again #'push doesn't work.


Solution

  • The macro gets the source form and computes the first result form: (push foo *observers*) and then this form is returned into the nirvana of the garbage collector, since it is not stored anywhere, not returned to any caller, not executed, ... It's garbage, immediately. So a clever compiler might even remove it...

    The macro form then computes the second form (defun ...). This form is returned from the macro and then later executed.

    Either you want to execute the first form at macro expansion time, then you would need to make it executable - by removing the backquote and comma.

    Or you want to include it into the generated source code, then you need to return a progn form, which encloses all the sub-forms.

    You also might want to think about the effects of PUSH vs. PUSHNEW, when a macro form gets executed / expanded multiple times...

    Unwanted evaluation of symbols when using macros

    Remember: symbols are variables in Lisp code. If you want to treat them as symbols as themselves, then you need to quote the symbol or the data structure, where they occur.

    Say, your form is:

    (add-observer below-80 ...)
    

    That meansbelow-80 is unquoted.

    Now you generate code where the symbol is unquoted, too.:

    (push below-80 ...)
    

    Naturally, this tries to evaluate the variable below-80, which seems to be unbound in your code.

    If you want below-80 treated to be a symbol during evaluation, you have to quote it: 'below-80. Your generated code should look like this:

    (push 'below-80 ...)
    

    Quote it either in your code

    (add-observer 'below-80 ...)
    

    or by the macro into the expansion:

    (push 'below-80 ...)
    

    The backquote template for that is

    `(progn
       (push ',name ...)
       ...)