我一直在为我的一个作业编写一些简单的测试用例,并使用宏构建了一些测试套件。我有run-test
和run-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
就足够了。但是,在递归宏调用的响应中有一些有用的信息。
答案 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))))))))
=> ...
嗯,实际上这会同时发生在两个递归分支上。所以它比例子更快爆炸。
更好的主意?
首先尝试编写与函数相同的东西。找出你必须将lambda用于延迟评估的位置。然后将函数抽象为宏,这样就可以省略lambdas。
顺便说一下,首先编写函数的一点是,有时候你会发现函数已经足够好了。编写一个函数可以执行的宏是一个错误。
通常,在Common Lisp中编写宏时,请先使用loop
而不是递归。递归宏很棘手(通常是错误的:)。
编辑:
这是一个更正确(但更长)的例子:
(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))))))