【发布时间】:2013-12-05 07:07:54
【问题描述】:
是否有一种干净的方式来实现将“到达”宏调用的动态范围?也许更重要的是,即使有,也应该避免吗?
这是我在 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"
在外部binding 调用上执行macroexpand-all(来自clojure.walk)让我相信“问题”(或功能,视情况而定)是(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,这似乎是错误的。解析宏中的符号似乎也是错误的——感觉就像是半途而废的eval。
最终,我正在为一种名为 qgame 的“语言”编写一个相对轻量级的解释器,并且我希望能够在解释器执行内容的上下文之外定义一些动态渲染函数。渲染功能可以对顺序指令调用和中间状态进行一些可视化。我正在使用宏来处理解释器执行的东西。到目前为止,我实际上已经完全不使用宏,而且我将渲染器函数作为我的执行函数的参数。无论如何,老实说,这种方式似乎更简单。
但我还是很好奇。这是 Clojure 的预期功能,即宏无法访问动态绑定?无论如何都有可能解决它(不诉诸黑魔法)?这样做有什么风险?
【问题讨论】:
标签: macros clojure dynamic-scope