是否有一种干净的方式来实现将“触及”宏调用的动态范围?也许更重要的是,即使有,也应该避免吗?
这是我在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的预期功能,宏是否无法访问动态绑定?是否有可能无论如何解决它(不使用黑暗魔法)?这样做有什么风险?
答案 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*
本身。