Common Lisp递归宏扩展

时间:2013-11-25 07:12:53

标签: 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 '(my-recursive-fact 5))
(* 5 (MY-RECURSIVE-FACT 4))
T

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

而且我仍然很想知道是否有办法将这种宏扩展到最后。

4 个答案:

答案 0 :(得分:9)

Slime有一个代码行走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)))))

答案 1 :(得分:8)

MACROEXPAND使用表单并展开它。它会多次执行,直到表单不再是宏形式。

在您的示例中,对my-recursive-fact的顶级调用是一种宏形式。前面乘法的结果形式不是宏形式,因为*不是宏。这是一个功能。表单有一个参数,它是一个宏形式。但MACROEXPAND并没有考虑那些。

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

答案 2 :(得分:1)

编辑:如果在特殊的运算符构造中,如果任何变量的名称与表单的 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)))))

答案 3 :(得分:1)

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