マクロ呼び出しに「到達」する動的スコープを実装するクリーンな方法はありますか? おそらくもっと重要なことは、たとえあったとしても、それを避けるべきでしょうか?
これが私が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"
外側の呼び出しで (used from ) を実行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))))
回避策での私の亀裂は次のとおりです。
(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 の意図した機能ですか? とにかく(ダークマジックに頼らずに)回避することは可能ですか?そうすることのリスクは何ですか?