在Clojure中的嵌套宏之间传递编译时状态 [英] Passing compile-time state between nested macros in Clojure
问题描述
我正在尝试编写一个可以全局和嵌套使用的宏,如下所示:
I'm trying to write a macro that can be used both in a global and nested way, like so:
;;; global:
(do-stuff 1)
;;; nested, within a "with-context" block:
(with-context {:foo :bar}
(do-stuff 2)
(do-stuff 3))
以嵌套方式使用时,do-stuff
应该可以访问由with-context
设置的{:foo :bar}
.
When used in the nested way, do-stuff
should have access to {:foo :bar}
set by with-context
.
我已经能够这样实现:
(def ^:dynamic *ctx* nil)
(defmacro with-context [ctx & body]
`(binding [*ctx* ~ctx]
(do ~@body)))
(defmacro do-stuff [v]
`(if *ctx*
(println "within context" *ctx* ":" ~v)
(println "no context:" ~v)))
但是,我一直在尝试将do-stuff
中的if
从运行时转换为编译时,因为是从with-context
主体内部调用还是在全局范围内调用do-stuff
是已经存在的信息在编译时可用.
However, I've been trying to shift the if
within do-stuff
from runtime to compile-time, because whether do-stuff
is being called from within the body of with-context
or globally is an information that's already available at compile-time.
不幸的是,我无法找到解决方案,因为嵌套宏似乎在多个宏扩展运行"中被扩展,因此*ctx*
的动态绑定(如在with-context
中设置的)不可用. do-stuff
展开时,将不再可用.所以这行不通:
Unfortunately, I've not been able to find a solution, because nested macros seem to get expanded in multiple "macro expansion runs", so the dynamic binding of *ctx*
(as set within with-context
) is not available anymore when do-stuff
gets expanded. So this does not work:
(def ^:dynamic *ctx* nil)
(defmacro with-context [ctx & body]
(binding [*ctx* ctx]
`(do ~@body)))
(defmacro do-stuff [v]
(if *ctx*
`(println "within context" ~*ctx* ":" ~v)
`(println "no context:" ~v)))
有什么想法可以做到这一点吗?
Any ideas how to accomplish this?
或者我的方法是完全疯狂的,并且有一种模式可以将状态从一个宏传递到一个嵌套的宏吗?
Or is my approach totally insane and there's a pattern for how to pass state in such a way from one macro to a nested one?
编辑:
with-context
的主体应该能够使用任意表达式,而不仅是do-stuff
(或其他上下文感知函数/宏).所以这样的事情也应该是可能的:
The body of with-context
should be able to work with arbitrary expressions, not only with do-stuff
(or other context aware functions/macros). So something like this should also be possible:
(with-context {:foo :bar}
(do-stuff 2)
(some-arbitrary-function)
(do-stuff 3))
(我知道some-arbitrary-function
是关于副作用的,例如它可能会向数据库中写入内容.)
(I'm aware that some-arbitrary-function
is about side effects, it might write something to a database for example.)
推荐答案
对代码进行宏扩展时,Clojure 定位点:
When the code is being macroexpanded, Clojure computes a fixpoint:
(defn macroexpand
"Repeatedly calls macroexpand-1 on form until it no longer
represents a macro form, then returns it. Note neither
macroexpand-1 nor macroexpand expand macros in subforms."
{:added "1.0"
:static true}
[form]
(let [ex (macroexpand-1 form)]
(if (identical? ex form)
form
(macroexpand ex))))
退出宏时,在宏执行期间建立的任何绑定都不再存在(这发生在macroexpand-1
内部).到扩展内部宏时,上下文早已不复存在.
Any binding you establish during the execution of a macro is no more in place when you exit your macro (this happens inside macroexpand-1
). By the time an inner macro is being expanded, the context is long gone.
但是,您可以直接调用macroexpand
,在这种情况下,绑定仍然有效.但是请注意,在您的情况下,您可能需要调用 macroexpand-all
.
此答案解释了macroexpand
和clojure.walk/macroexpand-all
之间的区别:基本上,您需要确保所有内部形式都被宏扩展.
macroexpand-all
的源代码显示了方式已实现.
But, you can call macroexpand
directly, in which case the binding are still effective. Note however that in your case, you probably need to call macroexpand-all
.
This answer explains the differences between macroexpand
and clojure.walk/macroexpand-all
: basically, you need to make sure all inner forms are macroexanded.
The source code for macroexpand-all
shows how it is implemented.
因此,您可以按以下方式实现您的宏:
So, you can implement your macro as follows:
(defmacro with-context [ctx form]
(binding [*ctx* ctx]
(clojure.walk/macroexpand-all form)))
在这种情况下,应该从内部宏内部看到动态绑定.
In that case, the dynamic bindings should be visible from inside the inner macros.
这篇关于在Clojure中的嵌套宏之间传递编译时状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!