Macros 非递归符号宏单元
我想将某个片段中的所有符号Macros 非递归符号宏单元,macros,lisp,common-lisp,metaprogramming,Macros,Lisp,Common Lisp,Metaprogramming,我想将某个片段中的所有符号x展开为(值x)。例如 (lambda () (* x x)) 应该成为 (lambda () (* (value x) (value x))) 简单使用symbol macrolet是行不通的,因为 (symbol-macrolet ((x (value x))) (lambda () (* x x))) 在宏扩展过程中,由于宏扩展再次处理符号小宏扩展的结果而分解为无限递归,包括相同的符号小宏 即使尝试将x扩展到(值y),然后再将y扩展到x也
x
展开为(值x)
。例如
(lambda ()
(* x x))
应该成为
(lambda ()
(* (value x) (value x)))
简单使用symbol macrolet
是行不通的,因为
(symbol-macrolet ((x (value x)))
(lambda ()
(* x x)))
在宏扩展过程中,由于宏扩展再次处理符号小宏
扩展的结果而分解为无限递归,包括相同的符号小宏
即使尝试将x
扩展到(值y)
,然后再将y
扩展到x
也行不通。例如:
(symbol-macrolet ((y x))
(symbol-macrolet ((x (value y)))
(lambda () (* x x))))
在SBCL上的宏扩展时间仍然崩溃
有没有一种方法可以在不进行完整代码遍历的情况下只扩展一次符号?这是在一篇文章中讨论的。一位用户jathd指出:
你观察到的行为来自于宏观扩张的工作方式
常规:处理表单进行评估(或编译,或
宏扩展,或…,如果是宏调用,请将其替换为
相应的展开,并从一开始就开始处理
用新的形式
所以你必须积极地为符号宏做一些特殊的事情,
与常规宏相反,它们不是“递归的”
帕斯卡尔·康斯坦扎提出了以下建议:
一个很好的解决方案是将符号宏扩展为正则表达式
宏
(macrolet ((regular-macro (...) ...))
(symbol-macrolet ((sym (regular-macro)))
...))
尽管informatimago指出,这仍然表现出与原作相同的行为:
如果将常规宏扩展包括在
macroexpandable放置命名符号macrolet的符号
不幸的是,“有没有一种方法可以在不进行完整代码遍历的情况下只扩展符号一次?”的答案似乎是“没有”。然而,解决这个问题并不难;链接线程中的“解决方案”最终使用gensyms来避免问题。例如:
(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
可以对symbol macrolet
进行阴影处理这一事实可能会很有用。这意味着您可以将扩展包装在一个let
中,以恢复所需的绑定:
(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
如果您发现自己经常这样做,您可以将其封装在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
当然,这只适用于已经具有词法绑定的变量。除了在宏扩展中传递环境对象外,CommonLisp在访问环境对象方面没有提供太多功能。如果您的实现提供了环境访问,那么可以让unshadowing symbol macrolet检查每个var
是否绑定到环境中,如果是,则提供本地阴影,如果不是,则不执行阴影
笔记
有趣的是,看看这篇文章的原作者Antsan对他们对宏观扩张过程如何运作的期望说了些什么:
我认为宏观扩张是通过反复进行宏观扩张来实现的
在源上,直到到达固定点。这样,符号宏指令集
如果被宏删除,则将自动非递归
扩张
比如:
(symbol-macrolet (a (foo a))
a)
macroexpand-1> (foo a)
macroexpand-1> (foo a) ; fixpoint
那里不需要特殊情况,尽管我猜这
宏扩展的算法将较慢
这很有趣,因为这正是Common Lisp的“编译器宏”的工作方式。文件中说:
- 与普通宏不同,编译器宏可以仅通过返回与原始宏相同的表单来拒绝提供扩展 (可通过使用&whole获得)
这在这里并没有什么帮助,因为符号宏无法选择返回什么;也就是说,没有传递给符号宏的参数,因此没有任何可以检查或用于影响宏扩展的内容。返回相同表单的唯一方法是类似于
(symbol macrolet((x))…)
,这与目的背道而驰。在扩展期间重命名x如何:(symbol macrolet((x(值y))…)?@cybevnm:好主意,但显然不起作用。参见编辑。我不久前也在reddit上问了这个问题,得到了一些很好的讨论: