Macros Lisp:宏可以是递归的吗?

Macros Lisp:宏可以是递归的吗?,macros,lisp,common-lisp,Macros,Lisp,Common Lisp,我最近开始用Lisp编写代码,对宏印象最深——它们是在编译时编写的,这是我所知道的任何其他语言都无法做到的(即,在保持原始结构的情况下生成代码) 关于优化:我在同一代码中添加了类型注释(很多“fixnum”)。一旦我添加了3或4个宏,我意识到我做错了-这就是宏的用途,不要重复你自己 ; whenever we want to indicate that the result of an operation ; fits in a fixnum, we macro expand (the fix

我最近开始用Lisp编写代码,对宏印象最深——它们是在编译时编写的,这是我所知道的任何其他语言都无法做到的(即,在保持原始结构的情况下生成代码)

关于优化:我在同一代码中添加了类型注释(很多“fixnum”)。一旦我添加了3或4个宏,我意识到我做错了-这就是宏的用途,不要重复你自己

; whenever we want to indicate that the result of an operation 
; fits in a fixnum, we macro expand (the fixnum (...))
(defmacro fast (&rest args)
  `(the fixnum ,args))
...
(cond
  (...)
  (t (let* ((forOrange (+ (aref counts 5)
                          (fast * 2 (aref counts 6))
                          (fast * 5 (aref counts 7))
                          (fast * 10 (aref counts 8))))
            (forYellow (+ (aref counts 3)
                          (fast * 2 (aref counts 2))
                          (fast * 5 (aref counts 1))
                          (fast * 10 (aref counts 0))))
…事实上,这是有效的:我没有在任何地方写很多“(fixnum(…)”),而是快速地在表达式前面加上“fast”——一切都很好

但是

我意识到,即使这样,事情也不应该停止:原则上,宏观“快速”应该。。。在这种情况下,将在评估的顶部调用:

            (forYellow (fast + (aref counts 3)
                          (* 2 (aref counts 2))
                          (* 5 (aref counts 1))
                          (* 10 (aref counts 0))))
…并且它应该在所有子表达式中递归地“plant”“(fixnum(…))”

这能做到吗? “defmacro”可以是递归的吗

更新:我在尝试这样做时遇到了一些非常奇怪的问题,因此我最终按照Rord的建议执行了以下操作-即实现了一个函数,在repl中测试了它,并从宏调用它:

(defun operation-p (x)
  (or (equal x '+) (equal x '-) (equal x '*) (equal x '/)))

(defun clone (sexpr)
  (cond
    ((listp sexpr)
     (if (null sexpr)
       ()
       (let ((hd (car sexpr))
             (tl (cdr sexpr)))
         (cond
           ((listp hd) (append (list (clone hd)) (clone tl)))
           ((operation-p hd) (list 'the 'fixnum (cons hd (clone tl))))
           (t (cons hd (clone tl)))))))
    (t sexpr)))

(defmacro fast (&rest sexpr)
  `(,@(clone sexpr)))
它在SBCL下工作良好:

$ sbcl
This is SBCL 1.0.52, an implementation of ANSI Common Lisp.
...
* (load "score4.cl")

T
* (setf a '(+ (1 2) (- 1 (+ 5 6)))
...
* (clone a)

(THE FIXNUM (+ (1 2) (THE FIXNUM (- 1 (THE FIXNUM (+ 5 6))))))

* (macroexpand '(fast + 1 2 THE FIXNUM (- 1 THE FIXNUM (+ 5 6))))

(THE FIXNUM (+ 1 2 THE FIXNUM (THE FIXNUM (- 1 THE FIXNUM (THE FIXNUM (+ 5 6))))))
T
除了一个副作用外,一切正常:CMUCL可以工作,但不再编译代码:

; Error: (during macroexpansion)
; Error in KERNEL:%COERCE-TO-FUNCTION:  the function CLONE is undefined.
哦,好吧:-)


更新:编译失败已在中解决。

确定。您可以编写一个宏,以递归方式遍历参数表单,并以您想要的方式对其进行转换,一次一个子表单。因为这比听起来稍微复杂一些,所以正确的方法是使用库进行代码遍历


包含
hu.dwim.walker
,这显然是code walker的改进版本。

宏不仅被调用,而且在使用时被扩展,因此在其自身定义中引用宏可能会变得混乱。但您不必这样做:宏可以调用常规函数,因此您可以编写一个常规函数来执行递归列表处理,然后从宏中使用它。

您肯定可以编写一个在自身扩展中使用自身的宏。这很有意义,而且编写COND宏是一种自然的方式,由于任意长的cond对列表,它具有规则的扩展结构,可以用通常的car/cdr或first/rest递归来表示

(defmacro new-cond (&rest cond-pairs)
  (cond      ;; you need cond to compile new-cond syntax, LOL!
    ((null cond-pairs) nil)
    ((atom cond-pairs) (error "new-cond: bad syntax!"))
    (t `(if ,(first (first cond-pairs))
           (progn ,@(rest (first cond-pairs)))
           (new-cond ,@(rest cond-pairs))))))


> (macroexpand-1 '(new-cond (1 2) (3 4)))

(IF 1 (PROGN 2) (NEW-COND (3 4)))
这与惰性列表处理非常类似
macroexpand
仅扩展外部宏,留下一个“promise”对象继续扩展。“promise”对象是尾部的宏调用
(NEW-COND(34))

当然,真正的宏扩展器将遍历整个表单并“强制”所有这些承诺(未扩展的宏调用),直到不再存在

我认为这种风格有一些优点,例如使用
macroexpand
可以轻松调试。你得到一个小的扩展。如果它的递归性质是显而易见的,那就是胜利。如果宏是这样的,你的大脑需要看到整个事情扩大,这是一个损失

在这里,我可以看到
(如果1(progn2)(NEW-COND(34))
是正确的编译。如果1为真,则评估表单列表(2)。否则,继续使用其他条件对。现在我们必须验证一对情况:

> (macroexpand-1 '(new-cond (3 4)))
(IF 3 (PROGN 4) (NEW-COND))
非常好,通过明显的检查,无对盒<代码>(NEW-COND),减少到零

其次,宏也可以在自己的代码中调用自身。因此,例如,如果我们是试图定义
cond
的Lisp实现者,有一种方法我们实际上可以逃脱:

(defmacro cond (&rest cond-pairs)
  (cond      ;; you need cond to compile new-cond syntax, LOL!
    ((null cond-pairs) nil)
    ((atom cond-pairs) (error "new-cond: bad syntax!"))
    (t `(if ,(first (first cond-pairs))
           (progn ,@(rest (first cond-pairs)))
           (new-cond ,@(rest cond-pairs))))))
如何在定义cond的宏中使用
cond
?答案是自举。在宏中展开的
cond
是一个现有的
cond
,我们不知何故已经拥有了它。此新定义与现有的
cond
扩展得很好,然后将其替换

这个过程可以重复。我们可以反复评估上述
defmacro
表单;先前评估刚刚安装的
cond
用于扩展新评估中出现的
cond

如果我们用一个现有的Lisp编译器对一个Lisp编译器进行boostapping,我们可能可以使用宿主Lisp的
cond
启动此过程,因此我们永远不需要第二个不依赖
cond
cond
宏的boostapping版本


请注意,要使其正常工作,我们的替换
cond
宏的主体必须在作为
cond
的新定义安装之前完全展开。如果宏定义的主体未展开,则此操作无法工作;然后它就变成了一个递归调用。换句话说,这不是递归。递归无法工作;宏不需要自身来展开自身。充其量,它可能需要一个现有的宏,该宏不是它本身,但恰好具有相同的名称。

风格建议:不要将要转换的表单拼接到宏调用中。将其作为一个子表单保存,即与
(fast*…)
相比,更喜欢
(fast*…)
。它使语义更简单(以案例
(fast 3)
-这个表单应该是什么意思?)并且迎合了读者对表单结构的期望。@Matthias:最初我是这样做的-但我意识到在这种情况下有明确的语义(即“(fast 3)”只能表示“3”)和。。。这使它更易于使用:您可以在代码中看到要强制转换为fixnum的位置,您不必搜索paren匹配:只需在前面键入“fast”。这在Scheme中很常见。@leppie Right。它甚至有内置的先例,比如
map
(map-bling-xs)
。但请注意,对于CL,情况并非如此,在CL中,您必须编写
(mapcar#bling xs)
。造成这种差异的原因是Scheme是一个Lisp-1,因此不存在关于什么的混淆