【问题标题】:expanding a parameter list in a common lisp macro在通用 lisp 宏中扩展参数列表
【发布时间】:2013-03-14 05:52:24
【问题描述】:

我正在尝试自学通用 lisp,并且作为宏编写练习,我正在尝试创建一个宏来定义任意深度的嵌套循环。我正在使用 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)))

顺便说一句,我最初使用 gensym 编写此宏来生成循环计数器 (ii, jj),但后来我意识到如果我无法访问正文中的计数器,该宏将毫无用处。

无论如何,我想概括宏以创建一个嵌套执行循环,该循环将嵌套到任意级别。这是我到目前为止所得到的,但它并不完全有效:

(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 (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 语句的重点是仅在最内层循环中执行主体。这对我来说似乎不是很优雅,也没有经过真正的测试(因为我还不能扩展参数列表),但这并不是这个问题的重点。

如何在宏中正确展开列表?是宏语法有问题,还是函数调用中列表的表达式有问题?任何其他 cmet 也将不胜感激。

提前致谢。

【问题讨论】:

  • 为什么不把计数器列表当作一个参数呢? (defmacro nested-do indices start end &body body),并且有(索引 = '(ii jj kk)
  • @ValekHalfHeart - 这也很好,但我认为代码与 (&rest 索引) 相比只是索引更能自我记录。当我做出改变时,我仍然无法让索引像我预期的那样扩展。
  • @huaiyuan - 感谢您的链接。那里有很多有趣的回应。您的回答简洁明了,我也喜欢 6502 的递归宏扩展。

标签: macros common-lisp


【解决方案1】:

这是一种方法 - 从底部(循环体)向上构建每个索引的结构:

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

【讨论】:

  • 内部索引应该从前一个索引的相应当前值开始。
  • 这很好用。谢谢。但是,我仍然很好奇为什么我上面发布的代码没有扩展。我想我在引用中一定遗漏了一些东西,我仍然没有经验。有什么想法吗?
  • 这一行有 2 个问题:(dolist ((index ,indices)) - dolist 不需要双亲 ((() 和 ,indices 扩展为 (ii jj kk),但它们应该扩展为 @987654327 @,否则它们被视为 ii 的函数应用程序,参数为 jjkk。所以该行应该是:(dolist (index (list ,@indices))
【解决方案2】:

[除了反对票之外,这确实有效,而且也很漂亮!]

只是为了清楚地说明宏定义的递归性质,这里是一个 Scheme 实现:

(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

请注意,对于所有 Common-Lisp 宏,您需要使用“gensym”模式来避免变量捕获。

【讨论】:

  • +1 表示递归宏。请注意,此宏不需要 gensym,因为没有将新符号引入环境中。
  • 正在引入本地“索引”。
  • index 没有被介绍。你的例子的扩展是(DO ((I 0 (1+ I))) ((> I 2)) (DO ((J 0 (1+ J))) ((> J 2)) (PROGN (PRINT (LIST I J)))))。里面没有index
  • 还有一点关于风格的小问题:我建议写,(foo)而不是(,@(foo))
  • 你和@clayton 是对的,没有引入符号。虽然,(foo) 可能有效,但我使用了(,@(car indices)),以便可以很容易地将外部括号视为(nested-do (...) ...) 的句法元素
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多