- android - 多次调用 OnPrimaryClipChangedListener
- android - 无法更新 RecyclerView 中的 TextView 字段
- android.database.CursorIndexOutOfBoundsException : Index 0 requested, 光标大小为 0
- android - 使用 AppCompat 时,我们是否需要明确指定其 UI 组件(Spinner、EditText)颜色
我想将某个片段中的所有符号 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) ; fixpointNo 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/
在他的书中ANSI Common Lisp ,第 320 页,Paul Graham 写道 macrolet :“就像 flet 一样,本地宏可能不会相互调用。” 也许我误解了这一点,但我想不出任何可
假设我有一个类 class,其中包含插槽 first 和 second。在我的函数中,我可以将一个变量绑定(bind)到这些插槽之一,例如 (symbol-macrolet ((var (first
有没有一种方法可以使用 macrolet 来做词法闭包之类的事情?我想要做的是使以下宏成为本地递归助手,它在每个组合上调用一个函数,而不是像现在在 repl 中调用宏那样生成一个列表结果: CL-US
我想将某个片段中的所有符号 x 展开为 (value x)。例如 (lambda () (* x x)) 应该变成 (lambda () (* (value x) (value x))) sy
我正在尝试在读取时构建哈希表(以及其他操作)。我不希望散列表具有全局范围(目前),所以我使用宏和 gensym 来实现。在宏 x 中,我定义了一个宏 s,它类似于 setf,但在哈希表中定义了一个条目
(print x) 准确地打印出我想要评估的内容,但是 (eval x) 失败了,但是如果我运行 x 它就可以了!我错过了什么? 请告诉我为什么这不起作用,或者我是否在做一些愚蠢的事情。 我正在尝试打
我是一名优秀的程序员,十分优秀!