Oli*_*ver 5 macros state nested clojure
我正在尝试编写一个可以全局和嵌套方式使用的宏,如下所示:
;;; global:
(do-stuff 1)
;;; nested, within a "with-context" block:
(with-context {:foo :bar}
(do-stuff 2)
(do-stuff 3))
Run Code Online (Sandbox Code Playgroud)
当以嵌套方式使用时,do-stuff应该具有{: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)))
Run Code Online (Sandbox Code Playgroud)
但是,我一直在努力将if内部do-stuff从运行时转移到编译时,因为无论do-stuff是从体内调用with-context还是全局调用都是在编译时已经可用的信息.
不幸的是,我无法找到解决方案,因为嵌套宏似乎在多个"宏扩展运行"中得到扩展,因此扩展时不再可用*ctx*(如设置中with-context)的动态绑定do-stuff.所以这不起作用:
(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)))
Run Code Online (Sandbox Code Playgroud)
任何想法如何实现这一目标?
或者我的方法是完全疯狂的,并且有一种模式,如何以这种方式将状态从一个宏传递到嵌套的一个?
编辑:
主体with-context应该能够使用任意表达式,而不仅仅是do-stuff(或其他上下文感知函数/宏).所以这样的事情也应该是可能的:
(with-context {:foo :bar}
(do-stuff 2)
(some-arbitrary-function)
(do-stuff 3))
Run Code Online (Sandbox Code Playgroud)
(我知道这some-arbitrary-function是关于副作用的,例如它可能会写一些数据库.)
(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))))
Run Code Online (Sandbox Code Playgroud)
当您退出宏时(这发生在 内部),您在宏执行期间建立的任何绑定都不再有效macroexpand-1。当扩展内部宏时,上下文早已消失。
但是,您可以macroexpand直接调用,这种情况下绑定仍然有效。但请注意,在您的情况下,您可能需要致电macroexpand-all.
这个答案macroexpand解释了和之间的区别clojure.walk/macroexpand-all:基本上,您需要确保所有内部形式都是宏扩展的。的源代码macroexpand-all显示了它是如何实现的。
因此,您可以按如下方式实现宏:
(defmacro with-context [ctx form]
(binding [*ctx* ctx]
(clojure.walk/macroexpand-all form)))
Run Code Online (Sandbox Code Playgroud)
在这种情况下,动态绑定应该从内部宏内部可见。