Macros 在公共lisp宏中展开参数列表

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))

我试图自学公共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))
       ,@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(…)…)