我正在尝试编写一个可以全局和嵌套使用的宏,如下所示:
;;; global:
(do-stuff 1)
;;; nested, within a "with-context" block:
(with-context {:foo :bar}
(do-stuff 2)
(do-stuff 3))
当以嵌套方式使用时,
do-stuff
应该可以访问{:foo :bar}
设置的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)))
但是,我一直在尝试将
if
中的do-stuff
从运行时转移到编译时,因为是从do-stuff
体内调用with-context
还是从全局调用*ctx*
是在编译时已经可用的信息。不幸的是,我无法找到解决方案,因为嵌套宏似乎在多个“宏扩展运行”中被扩展,因此当
with-context
扩展时,就不再可用do-stuff
的动态绑定(bind)(如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
的主体应该能够使用任意表达式,而不仅是some-arbitrary-function
(或其他上下文感知功能/宏)。所以这样的事情也应该是可能的:(with-context {:foo :bar}
(do-stuff 2)
(some-arbitrary-function)
(do-stuff 3))
(我知道ojit_code是关于副作用的,例如,它可能会向数据库中写入一些内容。)
最佳答案
当代码进行宏扩展时,Clojure computes和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))))
退出宏时,在宏执行期间建立的任何绑定(bind)都不再存在(这发生在
macroexpand-1
内部)。到扩展内部宏时,上下文早已不复存在。但是,您可以直接调用
macroexpand
,在这种情况下绑定(bind)仍然有效。但是请注意,在您的情况下,您可能需要调用 macroexpand-all
。This answer解释了
macroexpand
和clojure.walk/macroexpand-all
之间的区别:基本上,您需要确保所有内部形式都被宏扩展。macroexpand-all
的源代码显示how it is implemented。因此,您可以按以下方式实现宏:
(defmacro with-context [ctx form]
(binding [*ctx* ctx]
(clojure.walk/macroexpand-all form)))
在这种情况下,动态绑定(bind)应该从内部宏内部可见。