【问题标题】:Dynamic scope in macros宏中的动态范围
【发布时间】: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


    【解决方案1】:

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

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

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

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

    【讨论】:

      【解决方案2】:

      您需要引用*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"
      

      【讨论】:

      • 您的答案与另一个完全一样正确,但我认为 Leonid 首先回答了(一分钟),所以我接受了那个。有点随意,真的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多