Macros 在公共lisp宏中展开参数列表
我试图自学公共lisp,作为宏编写的练习,我试图创建一个宏来定义任意深度的嵌套do循环。我与sbcl合作,使用emacs和slime 首先,我编写了以下双循环宏:Macros 在公共lisp宏中展开参数列表,macros,common-lisp,Macros,Common Lisp,我试图自学公共lisp,作为宏编写的练习,我试图创建一个宏来定义任意深度的嵌套do循环。我与sbcl合作,使用emacs和slime 首先,我编写了以下双循环宏: (defmacro nested-do-2 (ii jj start end &body body) `(do ((,ii ,start (1+ ,ii))) ((> ,ii ,end)) (do ((,jj ,ii (1+ ,jj))) ((> ,jj ,end))
(defmacro nested-do-2 (ii jj start end &body body)
`(do ((,ii ,start (1+ ,ii)))
((> ,ii ,end))
(do ((,jj ,ii (1+ ,jj)))
((> ,jj ,end))
,@body)))
然后我可以使用它如下:
(nested-do-2 ii jj 10 20 (print (+ ii jj)))
(nested-do (ii jj kk) 10 15 (print (+ ii jj kk)))
顺便说一句,我最初使用gensym编写这个宏来生成循环计数器(ii,jj),但后来我意识到,如果我无法访问主体中的计数器,那么这个宏就毫无用处了
无论如何,我想对宏进行泛化,以创建嵌套到任意级别的嵌套do循环。到目前为止,我已经掌握了这一点,但它不太管用:
(defmacro nested-do ((&rest indices) start end &body body)
`(dolist ((index ,indices))
(do ((index ,start (1+ index)))
((> index ,end))
(if (eql index (elt ,indices (elt (reverse ,indices) 0)))
,@body))))
我想引用如下:
(nested-do-2 ii jj 10 20 (print (+ ii jj)))
(nested-do (ii jj kk) 10 15 (print (+ ii jj kk)))
但是,列表未正确展开,我最终在调试器中出现以下错误:
error while parsing arguments to DEFMACRO DOLIST:
invalid number of elements in
((INDEX (II JJ KK)))
如果不明显,则嵌入的if语句的要点是仅在最内部的循环中执行主体。对我来说,这似乎不是非常优雅,也没有经过真正的测试(因为我还不能扩展参数列表),但这并不是这个问题的重点
如何在宏中正确展开列表?是宏语法问题,还是函数调用中列表的表达式问题?任何其他意见也将不胜感激
提前感谢。这里有一种方法可以做到这一点-从底部(循环体)到每个索引构建结构:
(defmacro nested-do ((&rest indices) start end &body body)
(let ((rez `(progn ,@body)))
(dolist (index (reverse indices) rez)
(setf rez
`(do ((,index ,start (1+ ,index)))
((> ,index ,end))
,rez)))))
[除了反对票,这确实有效,而且也很漂亮!] 为了清楚地说明宏定义的递归性质,下面是一个方案实现:
(define-syntax nested-do
(syntax-rules ()
((_ ((index start end)) body)
(do ((index start (+ 1 index)))
((= index end))
body))
((_ ((index start end) rest ...) body)
(do ((index start (+ 1 index)))
((= index end))
(nested-do (rest ...) body)))))
使用上述内容作为模板,可以完成以下操作:
(defmacro nested-do ((&rest indices) start end &body body)
(let ((index (car indices)))
`(do ((,index ,start (1+ ,index)))
((> ,index ,end))
,(if (null (cdr indices))
`(progn ,@body)
`(nested-do (,@(cdr indices)) ,start ,end ,@body)))))
* (nested-do (i j) 0 2 (print (list i j)))
(0 0)
(0 1)
(0 2)
(1 0)
(1 1)
(1 2)
(2 0)
(2 1)
(2 2)
NIL
请注意,对于所有常见的Lisp宏,您需要使用“gensym”模式来避免变量捕获。为什么不将计数器列表视为单个参数<代码>(defmacro嵌套的do索引开始、结束和正文),以及have(索引=
”(ii jj kk)
)请参见。@ValekHalfHeart-这也可以,但我认为代码使用(&rest索引)比仅使用索引更具自文档性。当我做出改变时,我仍然无法让指数像我预期的那样扩展。@huaiyuan-谢谢链接。那里有很多有趣的反应。你的回答简洁明了,我也喜欢6502的递归宏扩展。内部索引应该从上一个索引的相应当前值运行。这非常有效。谢谢然而,我仍然很好奇为什么我上面发布的代码没有扩展。我想我一定是在引用中遗漏了什么,我还没有经验。有什么想法吗?这一行有两个问题:(dolist((index,index))
-dolist
不需要双亲((
)和,index
扩展为(ii jj kk)
,但它们应该扩展为(list ii jj kk)
,否则它们将被视为带有参数jj
和kk
的ii
的函数应用程序。因此,行应该是:(dolist(index(list,@index))
+1用于递归宏。请注意,此宏不需要gensym,因为没有向环境中引入新符号。正在引入本地“索引”。index
没有引入。示例的扩展是(DO((i0(1+I))(>i2))(DO((j0(1+J))(>j2))(PROGN(PRINT(LIST ij)))))
。里面没有索引
。还有一个关于风格的小问题:我建议写,(foo)
,而不是(,@(foo))
。你和@clayton是对的,没有引入任何符号。虽然,(foo)
可能有效,但我使用了(,@(汽车索引))
,这样就可以很容易地将外部paren看作(嵌套do(…)…)