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

Recursion 如何在&;上编写递归宏调用;Lisp中的REST参数?,recursion,macros,lisp,common-lisp,Recursion,Macros,Lisp,Common Lisp,我一直在为我的一个作业编写一些简单的测试用例,并使用宏构建了一个测试套件。我有运行测试和运行测试部分等等。 我想运行测试部分获取一些参数,这些参数是运行测试调用,并计算通过和失败的次数 runtest通过时返回T,失败时返回NIL 我现在要做的是编写一个宏,它接受一个&REST参数,并调用此列表的每个元素,最后返回真值的数目 这就是我目前拥有的: (defmacro count-true (&rest forms) `(cond ((null ,forms) 0)

我一直在为我的一个作业编写一些简单的测试用例,并使用宏构建了一个测试套件。我有
运行测试
运行测试部分
等等。 我想
运行测试部分
获取一些参数,这些参数是
运行测试
调用,并计算通过和失败的次数

runtest
通过时返回T,失败时返回NIL

我现在要做的是编写一个宏,它接受一个
&REST
参数,并调用此列表的每个元素,最后返回真值的数目

这就是我目前拥有的:

(defmacro count-true (&rest forms)
`(cond
    ((null ,forms)
      0)
    ((car ,forms)
      (1+ (count-true (cdr ,forms))))
    (T
      (count-true (cdr ,forms)))))
然而,这将我的REPL放入一个无限循环中。也许有人能指出我如何更有效地操纵这些论点。这是个好主意吗?有更好的方法吗

编辑:


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

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

请注意,表单列表末尾的测试从未计算过,而是表示为代码。它应该在backtick表达式之外。另一个人解释说,(cdr表单)也需要在宏扩展时进行评估,而不是作为代码进行编译

也就是说,类似这样的东西(未经测试):


在宏展开时,
cdr
不计算。因此,
(count true 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放置在何处以延迟评估。然后将函数抽象为宏,这样就可以省去lambda

    顺便说一句,首先编写函数的意义在于,有时您会发现函数已经足够好了。在函数可以执行的地方编写宏是一个错误

  • 通常,当您在Common Lisp中编写宏时,从循环开始,而不是递归。递归宏很棘手(通常是错误的:)

  • 编辑:

    下面是一个更正确(但更长)的示例:


    忘记递归宏吧。它们是一种痛苦,实际上只适用于高级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))
    

    关于宏是什么,我相信你有两种错误的印象

    宏用于扩展,函数用于执行。如果编写递归宏,它将递归展开,而不执行它生成的任何代码。宏根本不像内联函数


    编写宏时,它可以扩展为函数调用。当您编写宏时,您不会冒险进入功能不可用的“宏区域”。将
    count true
    作为宏编写是毫无意义的。

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

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

    这是一个很好的应用程序,便于宏递归:

    (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))))))
    
    `(+ (count-true ,CAR) (count-true ,*CDR))  
    
    测试:

    [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>)
          ...)
    
    好的,对于这种情况,我们会写:

    (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元形式:

    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
    递归的代码生成模板:

    (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))))))
    
    `(+ (count-true ,CAR) (count-true ,*CDR))  
    

    递归宏的思想通常是将其扩展为代码,代码再次使用该宏,但使用的参数更简单/更小。当宏以最简单的形式使用时,宏扩展将停止。所以递归是通过宏扩展机制完成的…谢谢你让我头脑清醒。我知道如何只用一个函数就可以完成我想要的。我的印象是,我必须使用宏,因为我认为我与计算表达式交互的方式。现在我明白我是怎么错的了。为什么不
    计数,用form
    代替
    +
    collect
    ?因为循环不计算结果。它生成代码。它在扩展后消失了。感谢您的回复。它直接回答了在递归宏中使用&rest参数的问题。感谢您的解释。我现在明白了我对宏观扩张的错误理解。使用
    循环
    的好提示。我已经学会了如何在这个任务中只使用一个函数。
    `(+ (count-true ,CAR) (count-true ,*CDR))