在Clojure中的嵌套宏之间传递编译时状态

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是关于副作用的,例如它可能会写一些数据库.)

cor*_*ump 4

当代码被宏展开时,Clojure计算一个固定点

(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)

在这种情况下,动态绑定应该从内部宏内部可见。