gpt4 book ai didi

macros - 非递归符号-macrolet

转载 作者:太空宇宙 更新时间:2023-11-03 18:36:31 25 4
gpt4 key购买 nike

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

(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 上的宏展开时仍然会崩溃。

有没有办法只展开一次符号而不进行完整的代码遍历?

最佳答案

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

The behaviour you observe comes from the way macroexpansion works in general: when processing a form for evaluation (or compilation, or macroexpansion, or...), if it is a macro invocation, replace it with the corresponding expansion and start the processing from the start with the new form.

So you would have to actively do something special for symbol macros, as opposed to regular macros, for them not to be "recursive".

Pascal Constanza 提供了这个建议:

A good solution is to make the symbol macro expand into a regular macro.

(macrolet ((regular-macro (...) ...)) 
(symbol-macrolet ((sym (regular-macro)))
...))

尽管 informatimago 指出这仍然表现出与原始行为相同的行为:

That would still fail if the regular macro expansion included in a macroexpandable place the symbol naming the symbol 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 中,以恢复您想要的绑定(bind):

(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

如果你发现自己经常这样做,你可以将它包装在符号宏小程序的“无阴影”版本中:

(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

当然,这只适用于已经具有词法绑定(bind)的变量。 Common Lisp 除了在宏扩展中传递它们之外,并没有提供太多访问环境对象的方式。如果您的实现提供环境访问,您可以让 unshadowing-symbol-macrolet 检查每个 var 是否绑定(bind)在环境中,如果是,则提供本地阴影,如果不是,则不进行阴影'

注意事项

有趣的是,看看该线程中的原作者 Antsan 是如何表达他们对宏扩展过程如何工作的期望的:

I thought macro expansion worked by repeatedly doing macro expansion on the source until a fixpoint is reached. This way SYMBOL-MACROLET would be automatically non-recursive if it was removed by macro expansion.

Something like:

(symbol-macrolet (a (foo a)) 
a)
macroexpand-1> (foo a)
macroexpand-1> (foo a) ; fixpoint

No special case would be needed there, although I guess that this algorithm for macro expansion would be slower.

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

  • Unlike an ordinary macro, a compiler macro can decline to provide an expansion merely by returning a form that is the same as the original (which can be obtained by using &whole).

这在这里并没有多大帮助,因为符号宏无法选择返回什么;也就是说,没有参数传递给符号宏,所以没有任何东西可以检查或用来影响宏展开是什么。返回相同表单的唯一方法是类似 (symbol-macrolet ((x x)) …),这违背了目的。

关于macros - 非递归符号-macrolet,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23394648/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com