非递归符号宏

时间:2014-04-30 17:39:11

标签: macros lisp common-lisp metaprogramming

我想将某个片段中的所有符号x展开为(value x)。 E.g。

(lambda ()
  (* x x))

应该成为

(lambda ()
  (* (value x) (value x)))

symbol-macrolet的简单使用不起作用,因为

(symbol-macrolet ((x (value x)))
  (lambda ()
    (* x x)))

在宏扩展期间爆炸成无限递归,因为symbol-macrolet扩展的结果会再次处理宏扩展,包括相同的symbol-macrolet

即使尝试将x扩展为(value y),然后y扩展为x也无效。例如:

(symbol-macrolet ((y x))
  (symbol-macrolet ((x (value y)))
    (lambda () (* x x))))

在SBCL的宏扩展时间仍然崩溃。

有没有办法只在没有完整代码行走的情况下扩展符号一次?

1 个答案:

答案 0 :(得分:8)

这是在comp.lang.lisp thread from 2013中讨论的。一位用户jathd指出:

  

您观察到的行为来自宏扩展的工作方式   general:处理表单以进行评估(或编译,或   macroexpansion,或...),如果是宏调用,请替换为   相应的扩展并从一开始就开始处理   用新表格。

     

所以你必须积极为符号宏做一些特别的事情,   而不是普通的宏,因为它们不是"递归"。

Pascal Constanza提供了这样的建议:

  

一个好的解决方案是使符号宏扩展为常规   宏。

(macrolet ((regular-macro (...) ...)) 
   (symbol-macrolet ((sym (regular-macro))) 
     ...))
但是,有些人指出,这仍然表现出与原作相同的行为:

  

如果常规宏扩展包含在a中,那仍然会失败   macroexpandable将符号命名为符号宏。

答案是“有没有办法只在没有完整代码行走的情况下扩展符号一次?”似乎是"不,"不幸。但是,要解决这个问题并不困难; "解决方案"在链接的线程中最终使用gensyms来避免问题。 E.g:

(let ((x 32))                            ; just to provide a value for x
  (let ((#1=#:genx x))                   ; new variable with x's value
    (symbol-macrolet ((x (values #1#)))  ; expansion contains the new variable
      (* x x))))                         ; === (* (values #1#) (values #1#))
;=> 1024

在宏展开中编写#1#或类似的东西并不是很有趣。如果您自动生成扩展,这并不算太糟糕,但如果您手动执行此操作,那么利用let可以影响{{1}的事实可能会有用}。这意味着您可以将扩展包装在恢复所需绑定的symbol-macrolet中:

let

如果你发现自己做了很多这样的事情,你可以把它包装成一个不起眼的" symbol-macrolet的版本:

(let ((x 32))
  (let ((#1=#:genx x))
    (symbol-macrolet ((x (let ((x #1#))    ; boilerplate
                           (values x))))   ; you get to refer to `x` here
      (* x x))))
;=> 1024

当然,这只适用于已经具有词法绑定的变量。除了在宏扩展中传递它们之外,Common Lisp在访问环境对象方面没有多少帮助。如果您的实现提供了环境访问,您可以使用unhadowing-symbol-macrolet检查每个(defmacro unshadowing-symbol-macrolet (((var expansion)) &body body) "This is like symbol-macrolet, except that var, which should have a binding in the enclosing environment, has that same binding within the expansion of the symbol macro. This implementation only handles one var and expansion; extending to n-ary case is left as an exercise for the reader." (let ((hidden-var (gensym (symbol-name var)))) `(let ((,hidden-var ,var)) (symbol-macrolet ((,var (let ((,var ,hidden-var)) ,expansion))) ,@body)))) (let ((x 32)) (unshadowing-symbol-macrolet ((x (values x))) (* x x))) ;=> 1024 是否在环境中绑定,如果是,则提供本地阴影,如果不是,则不进行阴影处理

备注

很有意思的是,Antsan在该主题中的原作者不得不说出他们对宏观扩展过程如何运作的期望:

  

我认为宏观扩张是通过反复进行宏观扩张来实现的   在源上,直到达到一个修复点。这种方式是SYMBOL-MACROLET   如果它被宏删除,它将自动非递归   扩张。

     

类似的东西:

var
     

那里不需要特殊情况,虽然我猜这个   宏扩展算法会慢一些。

这很有意思,因为这正是Common Lisp' 编译器宏工作。来自define-compiler-macro的文档说:

  
      
  • 与普通的宏不同,编译器宏只能通过返回与原始格式相同的表单来拒绝提供扩展   (可以通过使用& whole获得)。
  •   

这在这里真的没有用,因为符号宏不能选择返回什么;也就是说,没有任何参数传递给符号宏,因此没有什么可以检查或用来影响宏展开的内容。返回相同表单的唯一方法是类似(symbol-macrolet (a (foo a)) a) macroexpand-1> (foo a) macroexpand-1> (foo a) ; fixpoint ,这相当违背了目的。