3
votes

I am new to Lisp and I am reading through Doug Hoyte's Let Over Lambda and he presents Paul Graham's nif macro in Chapter 3. I was playing around with that and made these two macros:

(defmacro niffy (expr pos zero neg)
  `(cond ((plusp ,expr) ,pos)
         ((zerop ,expr) ,zero)
         (t ,neg)))

(defmacro niffy2 (expr pos zero neg)
  `(let ((x ,expr))
     (cond ((plusp x) ,pos)
           ((zerop x) ,zero
            (t ,neg)))))

When I do (macroexpand '(niffy2 10 "positive" "zero" "negative")), I get what I expect: (LET ((X 10)) (COND ((PLUSP X) "positive") ((ZEROP X) "zero" (T "negative"))))

But when I do (macroexpand '(niffy 10 "positive" "zero" "negative")) I just get the evaluated form "positive". Which confuses me because in niffy, cond is backquoted, so I thought that meant it wouldn't be evaluated. Evaluating both niffy and niffy2 without the macro expansion both work exactly as I expect, returning "positive", "zero", and "negative" for positive, zero, and negative values respectively.

2

2 Answers

3
votes

This is because cond is a macro. Try running the following:

(macroexpand
 '(cond ((plusp 10) "positive")
        ((zerop 10) "negative")
        (t "zero")))

In SBCL, I get this:

(IF (PLUSP 10)
    (PROGN "positive")
    (COND ((ZEROP 10) "negative") (T "zero")))
T

In CLISP, I get this:

"positive" ;
T

While I can understand CLISP's behavior, it seems a bit needless, since the optimization could more easily be handled after macroexpansion.

(Note that you should ordinarily prefer the version with let because it doesn't evaluate its argument multiple times.)

1
votes

Your code has an error:

(defmacro niffy2 (expr pos zero neg)
  `(let ((x ,expr))
     (cond ((plusp x) ,pos)
           ((zerop x) ,zero              <- missing parenthesis
            (t ,neg)))))                 <- T is not a function

Test:

(defun test (x)
  (niffy2 x "positive" "zero" "negative"))

The compiler complains:

The following function is undefined:
T which is referenced by TEST

It should be:

(defmacro niffy2 (expr pos zero neg)
  `(let ((x ,expr))
     (cond ((plusp x) ,pos)
           ((zerop x) ,zero)
           (t ,neg))))

But when I do (macroexpand '(niffy 10 "positive" "zero" "negative")) I just get the evaluated form "positive".

Since you call for a macro expansion, it can't be evaluated. It must be an effect of the macro expander in that implementation.

Note that MACROEXPAND is an iterative process. The form will be expanded. The result form then could be another macro form. It will be expanded, too. And again. Again. Until the result form is not a macro form. Note that this does not traverse the subforms. It's strictly doing iterative macro expansion of the top form.

Use MACROEXPAND-1

So if you want to see the effects of only your macro, call MACROEXPAND-1. This function will expand exactly once.

CL-USER 23 > (macroexpand-1 '(niffy 10 "positive" "zero" "negative"))
(COND ((PLUSP 10) "positive")
      ((ZEROP 10) "zero")
      (T "negative"))

CL-USER 24 > (macroexpand-1 '(COND ((PLUSP 10) "positive")
                                   ((ZEROP 10) "zero")
                                   (T "negative")))
(IF (PLUSP 10)
  (PROGN "positive")
  (IF (ZEROP 10)
     (PROGN "zero")
     (PROGN "negative")))
T