Omr*_*ein 2 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
Run Code Online (Sandbox Code Playgroud)
这个m-get-a宏不是我的实际目标,它只是我遇到的问题的一个简化版本.我花了一段时间才意识到,因为我一直在调试macroexpand,这使得一切看起来都很好:
user> (binding [*a* "boop"] (macroexpand '(m-get-a)))
> "boop"
Run Code Online (Sandbox Code Playgroud)
在外部调用上执行macroexpand-all(使用clojure.walk)binding会让我相信"问题"(或特征,视情况而定)是(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))))
Run Code Online (Sandbox Code Playgroud)
这是我的解决方法:
(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)))))
Run Code Online (Sandbox Code Playgroud)
它将使用相关的动态绑定评估单个宏表达式.但我不喜欢macroexpand在宏中使用,这似乎是错误的.在宏中解析符号似乎也是错误的 - 感觉就像是一半eval.
最后,我正在为一种名为qgame的"语言"编写一个相对轻量级的解释器,我希望能够在解释器执行的东西之外定义一些动态渲染函数.渲染功能可以执行顺序指令调用和中间状态的一些可视化.我正在使用宏来处理解释器执行的东西.截至目前,我实际上已经切换到根本不使用宏,并且我还将渲染器函数作为执行函数的参数.无论如何,老实说这种方式似乎更简单.
但我仍然很好奇.这是Clojure的预期功能,宏是否无法访问动态绑定?是否有可能解决它(不使用黑暗魔法)?这样做有什么风险?
你需要引用*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"
Run Code Online (Sandbox Code Playgroud)
在编译程序期间会发生宏扩展,因此预测当时动态变量的未来值是不可靠的.
但是,可能,您不需要*a*在宏扩展期间进行评估,只是想保持原样.这种情况*a*将在调用实际代码时进行评估.在这种情况下,你应该用`符号引用它:
(defmacro m-get-a [] `*a*)
Run Code Online (Sandbox Code Playgroud)
你的实现m-get-a导致clojure (m-get-a)在代码编译时替换它的值,这是核心绑定*a*,而我的wersion导致它替换(m-get-a)变量*a*本身.
| 归档时间: |
|
| 查看次数: |
527 次 |
| 最近记录: |