我正在尝试将列表传递给宏,例如:
(defmacro print-lst (lst)
`(progn
,@(mapcar #'(lambda (x) `(print ,x)) lst)))
(let ((lst '(1 2 3)))
(print-lst lst))
它捕获错误:“值 LST 不是 LST 类型”。
所以,我的问题是,这段代码有什么问题以及如何将列表传递给宏?
我正在尝试将列表传递给宏,例如:
(defmacro print-lst (lst)
`(progn
,@(mapcar #'(lambda (x) `(print ,x)) lst)))
(let ((lst '(1 2 3)))
(print-lst lst))
它捕获错误:“值 LST 不是 LST 类型”。
所以,我的问题是,这段代码有什么问题以及如何将列表传递给宏?
我不确定为什么要将其定义为宏而不是常规函数,但问题是宏不评估它们的参数。如果你给它一个词法变量的名字,它看到的只是名字('LST
),而不是绑定的值。它(正确地)抱怨该符号'LST
不是一个列表,因此不是MAPCAR
.
您可以将其称为(print-lst (1 2 3))
,但是您可以不使用宏而只做(mapc #'print lst)
你试图用你的宏做的是扩展一个文字列表。
不评估宏参数。所以,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))
.
另一种选择是使用编译器宏来补充函数,以内联调用,其中参数的评估是编译时的已知值,但再次查看您的实现是否关注编译器宏:
(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))
您的代码不必要地使用了宏。实际上,您可以像上面提到的那样 eval (mapcar #'print '(1 2 3)) 。您也可以使用不带列表引号的 (print-lst (1 2 3)),但不建议这样做,因为这不符合使用参数调用函数的一般做法。
总而言之,在调用宏时,如果您希望对其进行评估,则不应引用没有 eval 逗号 ',' 的符号,因为它将逐字替换到宏模板中。
例如
(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)
如果您对标题中的问题感兴趣,可以查看我对此的另一个答案: