这是一个related question,某种跟进。
假设我正在尝试使用宏来构建循环表达式,其中生成的循环表达式取决于参数是否为列表:
(defmacro testing-loop (var)
`(eval (append '(loop for x from 0 to 5)
(when (consp ,var) '(and y in ,var))
'(collect)
(if (consp ,var) '(y) '(x))))
这似乎有效:
CL-USER> (testing-loop 2)
(0 1 2 3 4 5)
CL-USER> (testing-loop (list 5 6 7))
(5 6 7)
但是当在词法闭包中应用这个宏时,它会崩溃:
CL-USER> (let ((bar (list 1 2 3)))
(testing-loop bar))
抛出未定义的变量:BAR
我希望testing-loop
宏扩展到绑定栏的词法范围?
答案 0 :(得分:4)
@mck,我明白为什么你现在要使用eval
。但是,正如我在上一个问题的答案中提到的那样,这是一个非常混乱的解决方案,速度很慢。经典的 On Lisp 对eval
:
“通常,在运行时调用eval不是一个好主意,原因有两个:
这是效率低下的:eval是一个原始列表,要么必须在它上面编译它 现场,或在口译员中评估。无论哪种方式都比编译慢 事先编写代码,然后调用它。
功能不强,因为表达式的评估没有词汇上下文。 除此之外,这意味着你不能参考普通的 在被评估的表达式之外可见的变量。
通常,明确地调用eval就像在机场礼品店购买东西一样。 等到最后一刻,你必须支付高价 选择二流商品。“
在这种情况下,最简单的事情就是:
(defmacro testing-loop (var)
(let ((g (gensym)))
`(let ((,g ,var))
(if (consp ,g)
(loop for x from 0 to 5 collect x)
(loop for x from 0 to 5 and y in ,g collect y)))))
我知道你想要分解公共loop for x from 0 to 5
(无论如何在第二个分支中实际上并不需要)。但是loop
本身就是一个宏,它在编译时将转换为高效的低级代码。因此,必须使用编译时可用的值在编译时构建对loop
的调用。您不能在其中插入(if)
,以便在运行时进行评估。
如果你真的不想重复loop for x from 0 to 5
,你可以这样做:
(let ((a '(loop for x from 0 to 5)))
`(if (consp ,var)
(,@a collect x)
(,@a and y in ,var collect y)))
这只是为了给你这个想法;如果你真的这样做,请确保gensym
!
从中学到的一个好教训是:在编写宏时,需要清楚地记住编译时发生的情况以及运行时发生的情况。您使用eval
编写的宏会根据loop
的返回值动态编译consp
宏,每次运行。你真的想编译两个不同的loop
宏,只需在运行时选择正确的宏。