Loops Common Lisp:如何使用宏构建循环表达式?
这是一个,某种形式的跟进 假设我试图使用宏构建循环表达式,其中生成的循环表达式取决于参数是否为列表:Loops Common Lisp:如何使用宏构建循环表达式?,loops,macros,common-lisp,Loops,Macros,Common Lisp,这是一个,某种形式的跟进 假设我试图使用宏构建循环表达式,其中生成的循环表达式取决于参数是否为列表: (defmacro testing-loop (var) `(eval (append '(loop for x from 0 to 5) (when (consp ,var) '(and y in ,var)) '(collect) (if (consp ,var) '
(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
我期望
测试循环
宏扩展到bar绑定的词法范围 @mck,我明白你为什么现在要使用eval
。但这是一个非常混乱的解决方案,而且很慢,正如我在回答前一个问题时提到的。Lisp上的经典之作是关于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)))))
我知道您希望将x的公共循环从0分解到5(在第二个分支中实际上并不需要)。但是loop
本身就是一个宏,它在编译时被转换成高效的低级代码。因此,对循环的调用必须在编译时生成,使用编译时可用的值。您不能只在其中插入一个(if)
,它将在运行时进行评估
如果您确实不想在0到5之间重复x的循环,可以执行以下操作:
(let ((a '(loop for x from 0 to 5)))
`(if (consp ,var)
(,@a collect x)
(,@a and y in ,var collect y)))
这只是给你一个想法;如果确实要这样做,请确保gensym
从中可以学到一个很好的教训:编写宏时,需要清楚地记住编译时发生的事情和运行时发生的事情。使用eval
编写的宏每次运行时都会根据consp
的返回值动态编译循环
宏。你真的想编译两个不同的循环
宏一次,然后在运行时选择正确的宏。@mck,我明白你为什么现在要使用eval
。但这是一个非常混乱的解决方案,而且很慢,正如我在回答前一个问题时提到的。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)))))
我知道您希望将x的公共循环从0分解到5(在第二个分支中实际上并不需要)。但是loop
本身就是一个宏,它在编译时被转换成高效的低级代码。因此,对循环的调用必须在编译时生成,使用编译时可用的值。您不能只在其中插入一个(if)
,它将在运行时进行评估
如果您确实不想在0到5之间重复x的循环,可以执行以下操作:
(let ((a '(loop for x from 0 to 5)))
`(if (consp ,var)
(,@a collect x)
(,@a and y in ,var collect y)))
这只是给你一个想法;如果确实要这样做,请确保gensym
从中可以学到一个很好的教训:编写宏时,需要清楚地记住编译时发生的事情和运行时发生的事情。使用eval
编写的宏每次运行时都会根据consp
的返回值动态编译循环
宏。您确实希望编译两个不同的循环
宏一次,并在运行时选择正确的宏。您可以在那里调用EVAL
EVAL
始终在空词汇环境中计算表单。坦白地说,我真的不明白为什么你一开始就在那里进行评估。@Elias Mårtenson:我认为eval
是在回答上一个问题时向mck建议的@麦克:如果你想通过找出宏的用例来学习宏,然后为这些用例找到解决方案,也许你应该阅读和/或。它们涵盖了很多有趣的领域——不过我建议从OnLisp开始。@EliasMårtenson:谢谢你的评论,我不知道eval
在空词汇环境中求值;我学到了一些新东西。但是,如果我没有将eval
放在那里,宏将返回代码列表。我做错了什么?@MironBrezuleanu:谢谢你分享消息来源!您有一个呼叫呼叫EVAL
EVAL
始终在空词汇环境中计算表单。坦白地说,我真的不明白为什么你一开始就在那里进行评估。@Elias Mårtenson:我认为eval
是在回答上一个问题时向mck建议的@麦克:如果你想通过找出t的用例来学习宏