2
votes

I'm reading Paul Graham's ANSI Common Lisp. In the chapter about macros he shows the following example:

(defmacro in (obj &rest choices)
  (let ((insym (gensym)))
   `(let ((,insym ,obj))
      (or ,@(mapcar #'(lambda (c) `(eql ,insym ,c))
                    choices))))) 

(Returns true if the first argument is equal to any of the other arguments)

He holds that it can't be written as a function. Wouldn't this function have the same functionality?

(defun in (obj &rest choices)
  (reduce (lambda (x y)
            (or x (eql y obj)))
          choices
          :initial-value nil))

The difference I see is that the macro will only evaluate arguments till it finds an eql argument. Is that it?

2
That's rather a large "Is that it?". If someone said "I can give you an IF that's not a macro but just a function! (But it always evaluates both branches.)", would you say "Is that it?"Asherah

2 Answers

4
votes

The point is, that the macro version evaluates the arguments lazily (it expands into an OR) stopping if a match is found. This cannot be achieved with a function, since a funcall will always evaluate all arguments first.

3
votes
> (macroexpand '(in 42
                    (long-computation-1)
                    (long-computation-2)
                    (long-computation-3)))

(LET ((#:G799 42))
  (OR (EQL #:G799 (LONG-COMPUTATION-1))
      (EQL #:G799 (LONG-COMPUTATION-2))
      (EQL #:G799 (LONG-COMPUTATION-3))))

To get the same effect you would need to write:

(defun in (obj &rest choices)
  (reduce (lambda (x y)
             (or x (eql (funcall y) obj)))
          choices
          :initial-value nil))

and use it this way:

(in 42
    (function long-computation-1)
    (function long-computation-2)
    (function long-computation-3))