代码之家  ›  专栏  ›  技术社区  ›  Willi Ballenthin

如何在Lisp中对a&rest参数编写递归宏调用?

  •  6
  • Willi Ballenthin  · 技术社区  · 16 年前

    我已经为我的一个任务编写了一些简单的测试用例,并且使用宏构建了一个测试套件。我有 run-test run-test-section 等等。 我想 运行测试部分 取一些参数 运行试验 调用并计算通过和失败的次数。

    运行试验 通过时返回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进入一个无限循环。也许有人能指出我如何更有效地操纵这些论点。这是个好主意吗?有更好的方法吗?

    编辑:

    如响应中所述,在这种情况下不需要宏。使用内置 COUNT 就够了。然而,在递归宏调用的响应中有有用的信息。

    6 回复  |  直到 9 年前
        1
  •  5
  •   Nathan Shively-Sanders    16 年前

    在宏观扩张时期, 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放在哪里来延迟评估。然后将函数抽象为宏,这样就可以省去lambda了。

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

    2. 通常,在用公共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))))))))
    
        2
  •  4
  •   Rainer Joswig mmmmmm    16 年前

    忽略递归宏。它们是一种痛苦,而且只适用于高级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))
    
        3
  •  3
  •   GoZoner    13 年前

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

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

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

    (defmacro count-true (&rest forms)
      (if forms
          `(if (car ',forms)
               (1+ (count-true ,@(cdr forms)))
             (count-true ,@(cdr forms)))
        0))
    
        4
  •  2
  •   Svante    16 年前

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

    宏是为扩展而编写的,函数是为执行而编写的。如果编写递归宏,它将递归地展开,而不执行它生成的任何代码。宏是 一点也不 有点像内联函数!

    编写宏时,它可以展开为函数调用。当你写宏的时候 进入功能不可用的“宏观领域”。写作毫无意义 count-true 作为一个宏。

        5
  •  1
  •   Kaz    9 年前

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

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

    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))  
    
        6
  •  0
  •   JohnMaraist    16 年前

    正如其他人所说,避免递归宏。如果你愿意在函数中这样做,你可以使用 apply :

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