Macros 宏中的动态范围

Macros 宏中的动态范围,macros,clojure,dynamic-scope,Macros,Clojure,Dynamic Scope,是否有一种干净的方法来实现动态范围,从而“达到”宏调用?也许更重要的是,即使有,是否应该避免 以下是我在回复中看到的内容: 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

是否有一种干净的方法来实现动态范围,从而“达到”宏调用?也许更重要的是,即使有,是否应该避免

以下是我在回复中看到的内容:

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
)会让我相信,“问题”(或功能,视情况而定)是
(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的“语言”编写一个相对轻量级的解释器,我希望能够在解释器执行内容的上下文之外定义一些动态渲染功能。渲染函数可以执行一些顺序指令调用和中间状态的可视化。我用宏来处理解释器的执行。到目前为止,我实际上已经切换到完全不使用宏,而且我还有renderer函数作为执行函数的参数。老实说,不管怎么说,这样做似乎简单多了


但我还是很好奇。这是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"

宏扩展是在编译程序的过程中进行的,因此无法预测当时动态变量的未来值

但是,在宏扩展期间,您可能不需要计算
*a*
,只想保持原样。在这种情况下,
*a*
将在调用实际代码时进行计算。在这种情况下,您应该用`符号:

(defmacro m-get-a [] `*a*)

您对
m-get-a
的实现导致clojure在编译代码时用其值替换
(m-get-a)
,这是
*a*
的核心绑定,而我的解决方案导致clojure将
(m-get-a)
替换为变量
*a*
本身。

您的答案与另一个答案完全一样正确,但我认为莱昂尼德先回答了(一分钟),所以我接受了这个答案。有点武断,真的。