宏中的动态范围

时间:2013-12-05 07:07:55

标签: macros clojure dynamic-scope

是否有一种干净的方式来实现将“触及”宏调用的动态范围?也许更重要的是,即使有,也应该避免吗?

这是我在REPL中看到的内容:

user> (def ^:dynamic *a* nil)
> #'user/*a*
user> (defn f-get-a [] *a*)
> #'user/f-get-a
user> (defmacro m-get-a [] *a*)
> #'user/m-get-a
user> (binding [*a* "boop"] (f-get-a))
> "boop"
user> (binding [*a* "boop"] (m-get-a))
> nil

这个m-get-a宏不是我的实际目标,它只是我遇到的问题的简化版本。我花了一段时间才意识到,因为我一直在调试macroexpand,这使得一切看起来都很好:

user> (binding [*a* "boop"] (macroexpand '(m-get-a)))
> "boop"

在外macroexpand-all电话上执行clojure.walk(从binding使用)会让我相信“问题”(或功能,视具体情况而定)是{{ 1}}在动态绑定之前得到评估:

(m-get-a)

以下是解决方法的问题:

user> (macroexpand-all '(binding [*a* "boop"] (f-get-a)))
> (let* []
    (clojure.core/push-thread-bindings (clojure.core/hash-map #'*a* "boop"))
    (try (f-get-a) (finally (clojure.core/pop-thread-bindings))))
user> (macroexpand-all '(binding [*a* "boop"] (m-get-a)))
> (let* []
    (clojure.core/push-thread-bindings (clojure.core/hash-map #'*a* "boop"))
    (try nil (finally (clojure.core/pop-thread-bindings))))

它将使用相关的动态绑定评估单个宏表达式。但我不喜欢在宏中使用(defmacro macro-binding [binding-vec expr] (let [binding-map (reduce (fn [m [symb value]] (assoc m (resolve symb) value)) {} (partition 2 binding-vec))] (push-thread-bindings binding-map) (try (macroexpand expr) (finally (pop-thread-bindings))))) ,这似乎是错误的。在宏中解析符号似乎也是错误的 - 感觉就像是一半的macroexpand

最终,我正在为一种名为qgame的“语言”编写一个相对轻量级的解释器,我希望能够在解释器执行内容的上下文之外定义一些动态呈现函数。渲染功能可以执行顺序指令调用和中间状态的一些可视化。我正在使用宏来处理解释器执行的东西。截至目前,我实际上已经切换到根本不使用宏,并且我还将渲染器函数作为执行函数的参数。无论如何,老实说这样看起来更简单。

但我仍然很好奇。这是Clojure的预期功能,宏是否无法访问动态绑定?是否有可能无论如何解决它(不使用黑暗魔法)?这样做有什么风险?

2 个答案:

答案 0 :(得分:5)

您需要引用*a*以使其有效:

user=> (def ^:dynamic *a* nil)
#'user/*a*
user=> (defmacro m-get-a [] `*a*)
#'user/m-get-a
user=> (binding [*a* "boop"] (m-get-a))
"boop"

答案 1 :(得分:5)

在程序编译过程中会发生宏扩展,所以当时预测动态变量的未来值是不可靠的。

但是,您可能不需要在宏扩展期间评估*a*,只是想保持原样。这种情况{@ 1}}将在调用实际代码时进行评估。在这种情况下,你应该用`symbol:

引用它
*a*

(defmacro m-get-a [] `*a*) 的实现会导致clojure在编译代码时将m-get-a替换为其值,这是(m-get-a)的核心绑定,而我的wersion会将其替换为{{} 1}}变量*a*本身。