在Clojure中的嵌套宏之间传递编译时状态 [英] Passing compile-time state between nested macros in Clojure

查看:85
本文介绍了在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 . 此答案解释了macroexpandclojure.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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆