如何在Lisp中的& REST参数上编写递归宏调用?

时间:2010-02-10 15:44:48

标签: recursion macros lisp common-lisp

我一直在为我的一个作业编写一些简单的测试用例,并使用宏构建了一些测试套件。我有run-testrun-test-section等等。 我希望run-test-section采用一些run-test次调用的参数,并计算PASS和FAIL的数量。

run-test在PASS上返回T,在失败时返回NIL。

我现在要做的是编写一个带有&REST参数的宏,并调用此列表的每个元素,最后返回TRUE值的数量。

这就是我目前所拥有的:

(defmacro count-true (&rest forms)
`(cond
    ((null ,forms)
      0)
    ((car ,forms)
      (1+ (count-true (cdr ,forms))))
    (T
      (count-true (cdr ,forms)))))

但是这会使我的REPL陷入无限循环。有人可能会指出我如何能更有效地操纵这些论点。这甚至是个好主意吗?有更好的方法吗?

编辑:

正如在回复中所指出的,在这种情况下不需要宏。使用内置的COUNT就足够了。但是,在递归宏调用的响应中有一些有用的信息。

6 个答案:

答案 0 :(得分:5)

在宏扩展时,不评估cdr。所以(count-true t t nil)会像这样无限扩展:

(count-true t t nil)
=>
(1+ (count-true (cdr (t t t nil))))
=>
(1+ (1+ (count-true (cdr (cdr (t t t nil))))))
=>
(1+ (1+ (1+ (count-true (cdr (cdr (cdr (t t t nil))))))))
=> ...

嗯,实际上这会同时发生在两个递归分支上。所以它比例子更快爆炸。

更好的主意?

  1. 首先尝试编写与函数相同的东西。找出你必须将lambda用于延迟评估的位置。然后将函数抽象为宏,这样就可以省略lambdas。

    顺便说一下,首先编写函数的一点是,有时候你会发现函数已经足够好了。编写一个函数可以执行的宏是一个错误。

  2. 通常,在Common Lisp中编写宏时,请先使用loop而不是递归。递归宏很棘手(通常是错误的:)。

  3. 编辑:

    这是一个更正确(但更长)的例子:

    (count-true t nil) =>
    (cond
      ((null '(t nil)) 0)
      ((car '(t nil)) (1+ (count-true (cdr '(t nil)))))
      (T (count-true (cdr '(t nil)))))
    =>
    (cond
      ((null '(t nil)) 0)
      ((car '(t nil)) (1+ (1+ (count-true (cdr (cdr '(t nil)))))))
      (T (count-true (cdr (cdr '(t nil))))))
    =>
    (cond
      ((null '(t nil)) 0)
      ((car '(t nil)) (1+ (1+ (1+ (count-true (cdr (cdr (cdr '(t nil)))))))))
      (T (count-true (cdr (cdr (cdr '(t nil)))))))
    =>
    (cond
      ((null '(t nil)) 0)
      ((car '(t nil)) (1+ (1+ (1+ (1+ (count-true (cdr (cdr (cdr (cdr '(t nil)))))))))))
      (T (count-true (cdr (cdr (cdr (cdr '(t nil))))))))
    

答案 1 :(得分:4)

忘记递归宏。它们很痛苦,实际上只适用于高级Lisp用户。

一个简单的非递归版本:

(defmacro count-true (&rest forms)
  `(+
    ,@(loop for form in forms
            collect `(if ,form 1 0))))

CL-USER 111 > (macroexpand '(count-true (plusp 3) (zerop 2)))
(+ (IF (PLUSP 3) 1 0) (IF (ZEROP 2) 1 0))

嗯,这是一个递归宏:

(defmacro count-true (&rest forms)
  (if forms
      `(+ (if ,(first forms) 1 0)
          (count-true ,@(rest forms)))
    0))

答案 2 :(得分:3)

问题是你的宏扩展为无限形式。系统将在宏扩展阶段尝试将其全部扩展,因此必须耗尽内存。

请注意,表单列表末尾的测试永远不会被评估,但表示为代码。它应该在反引号表达之外。另一个人解释说,(cdr表单)也需要在宏扩展时进行评估,而不是作为编译代码。

即。这样的事情(未经测试):

(defmacro count-true (&rest forms)
  (if forms
      `(if (car ',forms)
           (1+ (count-true ,@(cdr forms)))
         (count-true ,@(cdr forms)))
    0))

答案 3 :(得分:2)

我相信你对宏是什么有两个错误的印象。

宏是为扩展而编写的,用于执行的功能。如果你编写一个递归宏,它将递归展开,而不执行它产生的任何代码。一个宏根本不是类似于内联函数的东西!

编写宏时,它可以扩展为函数调用。当您编写宏时,冒险进入“宏区域”,其中功能将不可用。将count-true写成宏是没有意义的。

答案 4 :(得分:1)

这是一个很容易的宏递归应用程序:

(defmacro count-true (&rest forms)
  (cond
    ((null forms) 0)
    ((endp (rest forms)) `(if ,(first forms) 1 0))
    (t `(+ (count-true ,(first forms)) (count-true ,@(rest forms))))))

试验:

[2]> (count-true)
0
[3]> (count-true nil)
0
[4]> (count-true t)
1
[5]> (count-true nil t)
1
[6]> (count-true t nil)
1
[7]> (count-true t t)
2
[8]> (count-true nil nil)
0
[9]> (macroexpand '(count-true))
0 ;
T
[10]> (macroexpand '(count-true x))
(IF X 1 0) ;
T
[11]> (macroexpand '(count-true x y))
(+ (COUNT-TRUE X) (COUNT-TRUE Y)) ;
T
[12]> (macroexpand '(count-true x y z))
(+ (COUNT-TRUE X) (COUNT-TRUE Y Z)) ;
T

宏必须推理输入语法并生成执行计数的代码;你不能在生成代码和评估之间混淆。

当你这样做时,你马上就出错了:

`(cond ((null ,forms ...) ...)

您是否在运行时评估生成的代码模板中的元语法计算(我们在语法中有多少表单?)。在这个基本案例中你有正确的部分,但是它们是错误的。在我的解决方案中,我只在宏体中拥有cond,而不是在反引号中:

(cond ((null forms) ...) ...)

基本上:

(cond (<if the syntax is like this> <generate this>)
      (<if the syntax is like that> <generate that>)
      ...)

如果您不知道该怎么做,请写出您希望宏写入的代码。例如:

;; I want this semantics:
(if (blah) 1 0)   ;; count 1 if (blah) is true, else 0

;; But I want it with this syntax:
(count-true (blah))

好的,对于这个确切的情况,我们会写:

(defmacro count-true (single-form)
  `(if ,single-form 1 0))

完成!现在假设我们想要支持(count-true)而根本没有任何形式。

Wanted Syntax                   Translation

(count-true)                    0
(count-true x)                  (if x 1 0)

当有表格时,if展开会停留,但是当没有表格时,我们只想要一个恒定的零。简单,使参数可选:

(defmacro count-true (&optional (single-form nil have-single-form))
   (if have-single-form
     `(if ,single-form 1 0)  ;; same as before
      0))                    ;; otherwise zero

最后,扩展到N-ary形式:

Wanted Syntax                   Translation

(count-true)                    0
(count-true x)                  (if x 1 0)
(count-true x y)                (+ (if x 1 0) (if y 1 0))
                                   ^^^^^^^^^^ ^^^^^^^^^^

但是!现在我们注意到带下划线的术语对应于单个案例的输出。

(count-true x y)                (+ (count-true x) (count-true y))

哪个概括

(count-true x y z ...)          (+ (count-true x) (count-true y z ...))

这与使用car/cdr递归的代码生成模板直接对应:

`(+ (count-true ,CAR) (count-true ,*CDR))  

答案 5 :(得分:0)

正如其他人所说,避免使用递归宏。如果您愿意在函数中执行此操作,则可以使用apply

(defun count-true (&rest forms)
  (cond
    ((null forms) 0)
    (t (+ 1 (apply #'count-true (cdr forms))))))