Macros 公共Lisp递归宏扩展

Macros 公共Lisp递归宏扩展,macros,common-lisp,Macros,Common Lisp,很久以前,我在玩宏时想到了这个: (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 '(m

很久以前,我在玩宏时想到了这个:

(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-1
macroexpand
之间没有区别。我确信我在理解
宏扩展
时遗漏了一些关键点,而HyperSpec并没有特别提到递归宏


而且我仍然很好奇,是否有办法将这种宏扩展到它的末尾。

MACROEXPAND
采用一种形式并扩展它。它会多次执行此操作,直到窗体不再是宏窗体为止

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


如果要在所有级别上扩展代码,则需要使用代码遍历器。有些Lisp在IDE中可以直接访问它,如Lispworks。

Slime有一个代码遍历
Slime宏扩展全部
命令:

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

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

编辑:如果在特殊的运算符构造中有任何变量的名称与窗体的宏的车内位置相同,则此操作将中断。例如:
(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中测试):

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


您还可以使用
sb-cltl2:macroexpand all

CL-USER>(sb-cltl2:macroexpand all'(我的递归事实5))
(* 5 (* 4 (* 3 (* 2 (* 1 1)))))

首先,我误解了宏形式的含义。HypepSpec明确表示它是一个表单,第一个元素是宏名。其次,事实证明,我选择的实现——SBCL——有自己的代码遍历器。所以我需要的是sb walker:walk form facility,它给了我想要的输出:(*5(*4(*3(*2(*11)()))这似乎是一个不适合教学生宏的例子。您可以删除该宏中的每个撇号、逗号和反勾号,将其更改为函数,它的计算结果将完全相同。(事实上,你应该这么做。)我无意冒犯你,我理解你想要一个简单的例子,但是,向人们展示愚蠢的宏似乎是一种快捷的方式,不仅可以模糊他们对概念的理解,而且可以将它们转换为整个语言。如果你将一个
dolist
变量称为
incf
,即宏的名称,会发生什么情况?@DanRobertson说得很好。因为我们会根据扩展的结果重复出现,所以这应该不是问题,至少对于宏来说是这样。但是,期望表单的car位置出现变量的特殊运算符(如
let
)肯定会崩溃。我将编辑我的答案来解决这个问题。我在写这篇文章时提到的一点是,要“正确”地完成这项工作,需要一个代码行者。如果你得到一个macrolet,事情就更难了。@DanRobertson哦,我现在明白了。。。是的,这是一个相当幼稚的答案,我实际上没有太多的经验。问题是,在portable Common Lisp中实际上不可能回答这个问题,因为您无法通过需要
&environment
的defmacro进行宏扩展,因为这样的值无法构造
(recursive-macroexpand '(my-recursive-fact 5))))

 => (* 5 (* 4 (* 3 (* 2 (* 1 1)))))
(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)))))