【问题标题】:Common Lisp recursive macro expansionCommon Lisp 递归宏扩展
【发布时间】:2013-12-09 19:10:00
【问题描述】:

曾几何时,我在玩宏并想出了这个:

(defmacro my-recursive-fact (n)
  (if (= 0 n) '1
    (let ((m (1- n)))
      `(* ,n (my-recursive-fact ,m)))))

它奏效了。

CL-USER> (my-recursive-fact 5)
120

然后我认为这可能是向学生展示递归示例的好方法,如果我使用 macroexpand 扩展此宏:

CL-USER> (macroexpand '(my-recursive-fact 5))
(* 5 (MY-RECURSIVE-FACT 4))
T

也就是说,在这种情况下,macroexpand-1macroexpand 之间没有区别。我确信我在理解 macroexpand 时遗漏了一些关键点,HyperSpec 并没有说明递归宏有什么特别之处。

而且我仍然很想知道是否有办法将这种宏扩展到它的末尾。

【问题讨论】:

  • 这似乎是一个不适合向学生教授宏的示例。您可以删除该宏中的每个撇号、逗号和反引号,将其更改为一个函数,它的计算结果完全相同。 (事实上​​,你应该这样做。)我无意冒犯,我理解对一个简单示例的渴望,但是向人们展示 asinine 宏似乎是一种快速的方法,不仅可以混淆他们对概念的理解,而且可以将它们关闭到语言作为一个整体。

标签: macros common-lisp


【解决方案1】:

MACROEXPAND 采用 form 并将其展开。它会重复多次,直到表单不再是宏表单。

在您的示例中,对my-recursive-fact 的顶级调用是一个宏形式。前面有乘法的结果形式不是宏形式,因为* 不是宏。它是一个函数。该表单有一个参数,它是一个宏表单。但是MACROEXPAND 不看那些。

如果你想在所有层次上扩展代码,你需要使用 code walker。有些 Lisp 可以在 IDE 中直接访问它,比如 Lispworks。

【讨论】:

  • 首先,我误解了宏形式的含义。 HypepSpec 明确表示它是一个表单,第一个元素是一个宏名称。其次,事实证明我选择的实现 - SBCL - 有它自己的代码步行器。所以我需要的是 sb-walker:walk-form 工具,它给了我想要的输出:(* 5 (* 4 (* 3 (* 2 (* 1 1)))))
【解决方案2】:

史莱姆有一个代码行走slime-macroexpand-all 命令:http://common-lisp.net/project/slime/doc/html/Macro_002dexpansion.html

这可能是未记录和/或不受支持的,但也许您可以从 REPL 中调用它:

CL-USER> (swank-backend:macroexpand-all '(my-recursive-fact 5))
(* 5 (* 4 (* 3 (* 2 (* 1 1)))))

【讨论】:

    【解决方案3】:

    编辑:如果在特殊的运算符构造中,在表单的 car 位置有任何名称与宏的名称相同的变量,这将中断。例如: (let ((setf 10)) (print setf))


    这已经很老了,但是如果偶然发现这个问题的人希望有一种可移植的方式来递归扩展宏:

    (defun recursive-macroexpand (form)
      (let ((expansion (macroexpand form)))
        (if (and (listp expansion) (not (null expansion)))
          (cons (car expansion) (mapcar #'recursive-macroexpand (cdr expansion)))
          expansion)))
    

    例如(在 SBCL 和 CLISP 中测试):

    (recursive-macroexpand '(my-recursive-fact 5))))
    
     => (* 5 (* 4 (* 3 (* 2 (* 1 1)))))
    

    一个更丑陋的例子(常规的macroexpand 将保留第二个dolist 不变):

    (recursive-macroexpand
      '(dolist (x '(0 1))
        (dolist (y '(0 1))
          (format t "decimal: ~a binary: ~a~a~%" (+ (* x 2) (* y 1)) x y))))
    
     => (block nil
         (let* ((#:list-8386 '(0 1)) (x nil)) nil
          (tagbody #:loop-8387 (if (endp #:list-8386) (go #:end-8388)) (setq x (car #:list-8386))
           (block nil
            (let* ((#:list-8389 '(0 1)) (y nil)) nil
             (tagbody #:loop-8390 (if (endp #:list-8389) (go #:end-8391)) (setq y (car #:list-8389)) (format t "decimal: ~a binary: ~a~a~%" (+ (* x 2) (* y 1)) x y)
              (setq #:list-8389 (cdr #:list-8389)) (go #:loop-8390) #:end-8391 (return-from nil (progn nil)))))
           (setq #:list-8386 (cdr #:list-8386)) (go #:loop-8387) #:end-8388 (return-from nil (progn nil)))))
    

    【讨论】:

    • 如果您将dolist 变量之一称为incf,即宏的名称,会发生什么情况?
    • @DanRobertson 好点。由于我们重复展开的result,这应该不是问题,至少对于宏来说是这样。但是在表格的汽车位置中期望变量的特殊运算符(如let)肯定会中断。我将编辑我的答案来解决这个问题。
    • 我在写这篇文章时暗示的是,“正确地”执行此操作需要一个 code walker。如果你得到一个宏,事情就更难了。
    • @DanRobertson 哦,我现在明白了......是的,这是一个非常幼稚的答案,实际上我不是很有经验
    • 问题是在可移植的 Common Lisp 中实际上是不可能回答的,因为你不能通过需要 &environment 的 defmacro 进行宏扩展,因为无法构造这样的值
    【解决方案4】:

    你也可以使用sb-cltl2:macroexpand-all

    CL-USER> (sb-cltl2:macroexpand-all '(my-recursive-fact 5))
    (* 5 (* 4 (* 3 (* 2 (* 1 1)))))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-28
      • 1970-01-01
      相关资源
      最近更新 更多