How do I evaluate form inside lambda that is within a macro?

Viewed 62

I'm having problems with the following macro:

(defmacro gather-params (&rest body)
  "Return plist of params"
  `(concatenate 'list
        (map 'list
             #'(lambda (plist)
                 (if (typep (first plist) 'keyword)
                     (cons 'list plist)
                     plist))
             ',body)))

Within the macro, I can't make plist evaluate by adding a comma in front of it, e.g: ,plist when I do that, the compiler complains that the variable ,plist does not exist.

Is there something I'm not understand about the scope within a macro?

Current result:

input:  (gather-params (:mykey (+ 1 1)) (list 1 2 3))
result:   ((LIST :MYKEY (+ 1 1)) (LIST 1 2 3))

Desired result:

input:  (gather-params (:mykey (+ 1 1)) (list 1 2 3))
result:   ((LIST :MYKEY 2) (LIST 1 2 3))
1 Answers

(concatenate 'list '(1 2 3)) is just (copy-list '(1 2 3))

-> which both evaluate to (1 2 3).

The macro generates code. It has a backquoted list. This backquoted list is computed at macroexpansion time -> when the macro expansion happens, for example during compilation.

The generated code has a function with a parameter called plist. This function executes at runtime. It does not exist during macroexpansion. Thus plist is no variable during macroexpansion. Thus you can't compute a value of plist during macroexpansion, since the variable does not exist then.

If you want to evaluate code, then Common Lisp has the function eval for that. One can for example call eval at runtime.

CL-USER 31 > (defmacro gather-params (&rest body)
              "Return plist of params"
              `(map 'list
                    #'(lambda (plist)
                        (if (typep (first plist) 'keyword)
                            (list 'list
                                  (first plist)
                                  (eval (second plist)))
                            plist))
                    ',body))
GATHER-PARAMS

CL-USER 32 > (gather-params (:mykey (+ 1 1)) (list 1 2 3))
((LIST :MYKEY 2) (LIST 1 2 3))

Check out this example, with a different approach:

CL-USER 42 > (defmacro gather-params (&rest body)
               "Return plist of params"
               `(list ,@(mapcar
                         (lambda (plist)
                           (if (typep (first plist) 'keyword)
                               (list 'list
                                     ''list
                                     (first plist)
                                     (second plist))
                             plist))
                         body)))
GATHER-PARAMS

CL-USER 43 > (let ((foo 1))
               (gather-params (:mykey (+ foo foo)) (list 1 2 3)))
((LIST :MYKEY 2) (1 2 3))
Related