650*_*502 6 lisp macros metaprogramming common-lisp
我想x
将某个片段中的所有符号展开(value x)
.例如
(lambda ()
(* x x))
Run Code Online (Sandbox Code Playgroud)
应该成为
(lambda ()
(* (value x) (value x)))
Run Code Online (Sandbox Code Playgroud)
简单的使用是symbol-macrolet
行不通的,因为
(symbol-macrolet ((x (value x)))
(lambda ()
(* x x)))
Run Code Online (Sandbox Code Playgroud)
在宏扩展期间爆炸成无限递归,因为扩展的结果symbol-macrolet
再次被处理以用于宏扩展,包括相同的扩展symbol-macrolet
.
甚至试图扩大x
至(value y)
,然后y
到x
不工作.例如:
(symbol-macrolet ((y x))
(symbol-macrolet ((x (value y)))
(lambda () (* x x))))
Run Code Online (Sandbox Code Playgroud)
在SBCL的宏扩展时间仍然崩溃.
有没有办法只在没有完整代码行走的情况下扩展符号一次?
这是在2013年的comp.lang.lisp线程中讨论的.一位用户jathd指出:
您观察到的行为来自宏扩展的工作方式:处理表单以进行评估(或编译,或宏展开,或......)时,如果是宏调用,请将其替换为相应的扩展,并从中开始处理从新表格开始.
所以你必须积极地为符号宏做一些特殊的事情,而不是普通的宏,因为它们不是"递归的".
Pascal Constanza提供了以下建议:
一个好的解决方案是使符号宏扩展为常规宏.
Run Code Online (Sandbox Code Playgroud)(macrolet ((regular-macro (...) ...)) (symbol-macrolet ((sym (regular-macro))) ...))
虽然informatimago指出这仍然表现出与原作相同的行为:
如果常规宏扩展包含在宏扩展位置,则该符号将命名为符号宏,这仍然会失败.
不幸的是,"有没有一种方法可以在没有完整的代码行走的情况下扩展符号一次?"似乎是"不".但是,要解决这个问题并不困难; 链接线程中的"解决方案"最终使用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
Run Code Online (Sandbox Code Playgroud)
#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
Run Code Online (Sandbox Code Playgroud)
如果你发现自己经常这么做,你可以将它包装在symbol-macrolet的"unhadowing"版本中:
(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
Run Code Online (Sandbox Code Playgroud)
当然,这只适用于已经具有词法绑定的变量.除了在宏扩展中传递它们之外,Common Lisp不提供访问环境对象的方式.如果您的实现提供了环境访问,则可以使用unshadowing-symbol-macrolet检查每个var
是否在环境中绑定,如果是,则提供本地阴影,如果不是则不提供阴影.
有趣的是,看看这个主题的原作者Antsan不得不说出他们对宏扩展过程如何工作的期望:
我认为宏扩展通过在源上反复进行宏扩展直到达到修复点为止.这样,如果通过宏扩展删除了SYMBOL-MACROLET,它将自动是非递归的.
就像是:
Run Code Online (Sandbox Code Playgroud)(symbol-macrolet (a (foo a)) a) macroexpand-1> (foo a) macroexpand-1> (foo a) ; fixpoint
那里不需要特殊情况,虽然我猜这种宏扩展算法会慢一些.
这很有意思,因为这正是Common Lisp的编译器宏的工作方式.来自的文件define-compiler-macro
说:
- 与普通的宏不同,编译器宏只能通过返回与原始格式相同的格式(可以通过使用&whole获得)来拒绝提供扩展.
这在这里确实没有用,因为符号宏无法选择返回的内容; 也就是说,没有参数传递给符号宏,因此没有什么可以检查或用来影响宏展开的内容.返回相同形式的唯一方法就是这样(symbol-macrolet ((x x)) …)
,而不是失败的目的.