如何将列表传递给常见的lisp中的宏?

时间:2012-11-02 13:21:35

标签: common-lisp

我正在尝试将列表传递给宏,例如:

(defmacro print-lst (lst)
  `(progn
     ,@(mapcar #'(lambda (x) `(print ,x)) lst)))
(let ((lst '(1 2 3)))
      (print-lst lst))

它发现错误:“LST值不是LST类型。”

所以,我的问题是,这段代码有什么问题以及如何将列表传递给宏?

3 个答案:

答案 0 :(得分:7)

我不确定为什么要将其定义为宏而不是常规函数,但问题是宏不会​​评估它们的参数。如果你给它一个词法变量的名称,它所看到的只是名称('LST),而不是绑定值。正在抱怨(正确)符号'LST不是列表,因此不是MAPCAR的有效第二个参数。

您可以将其称为(print-lst (1 2 3)),但之后您可以不使用宏而只需执行(mapc #'print lst)

答案 1 :(得分:3)

您尝试对宏执行的操作是扩展文字列表。

不评估宏参数。因此,print-lst实际上是接收符号lst,而不是绑定到变量的列表。

您要么知道并给print-lst一个文字列表,要么您可以生成用于评估宏参数的代码:

(defmacro print-lst (lst)
  (let ((item (gensym)))
    ;; Macros usually make sure that expanded arguments are
    ;; evaluated only once and in left-to-right order.
    ;; 
    ;; In this case, we only have one argument and we only evaluate it once.
    `(dolist (,item ,lst)
       (print ,item))))

虽然这显然不是宏的一个很好的例子,但它最好是一个函数:

(defun print-lst (lst)
  (dolist (item lst)
    (print item)))

如果您希望内联print-lst来电,可以参考您的实施文档,了解它是否注意(declaim (inline print-lst))

另一种选择是使用编译器宏(作为函数的补充)来内联调用,其中参数的评估在编译时是已知值,但再次看看您的实现是否关注编译器宏: / p>

(define-compiler-macro print-lst (&whole form lst &environment env)
  (declare (ignorable env))
  ;; Some implementations have an eval function that takes an environment.
  ;; Since that's not standard Common Lisp, we don't use it in constantp.
  (if (constantp lst)
      `(progn
         ,@(mapcar #'(lambda (item)
                       `(print ,item))
                   (eval lst)))
      ;; Return the original form to state you didn't transform code.
      form))

答案 2 :(得分:0)

您的代码不必要地使用宏。实际上,你可以像上面提到的那样eval(mapcar#'print'(1 2 3))。您也可以使用(print-lst(1 2 3))而不使用列表引号,但不推荐使用它,因为这不适合通用实践来调用带参数的函数。

总而言之,在调用宏时,如果您希望对其进行评估,则不应引用没有eval逗号''的符号,因为它将字面上替换为进入宏模板。

e.g。

(setq a 1)

;;A won't be evaluated without the comma
(defmacro foo (x) `(progn ,(print x))
(foo 'a) ;;=> 'A (A is returned since (print 'a) is evaluated)
(foo a) ;;=> A (1 is returned since (print a) is evaluated)

;;A is evaluated
(defmacro bar (x) `(print ,x))
(bar 'a) ;;=> A
(bar a) ;;=> 1

您的代码:

(defmacro print-lst (lst)                                                                                    
  `(progn                                                                                                    
     ,@(mapcar #'(lambda (x) `(print ,x)) lst)))

'mapcar'表单将作为一个整体进行评估,但'lst'将仅替换您传入的内容。

我可以定义一个函数而不是一个宏来解决问题:

(defun print-lst (lst)
  (mapcar #'eval
    (mapcar #'(lambda (x) `(print ,x)) lst)))

然后:

(let ((lst '(1 2 3)))
  (print-lst lst))

;;=>
1
2
3
(1 2 3)

如果您对标题中的问题感兴趣,可以看到我的另一个答案:

How do I apply "or" to a list in elisp