web-dev-qa-db-ja.com

clojureと^:dynamic

動的変数とバインディング関数を理解しようとしたので、これを試しました(clojure 1.3):

user=> (defn f [] 
           (def ^:dynamic x 5) 
           (defn g [] (println x)) 
           (defn h [] (binding [x 3] (g))) 
           (h))
#'user/f
user=> (f)     
5
nil

混乱して、私はこのやや単純なコードを試しました:

user=> (def ^:dynamic y 5)
#'user/y
user=> (defn g [] (println y))
#'user/g
user=> (defn h [] (binding [y 3] (g)))
#'user/h
user=> (h)
3
nil

2つのコードの違いは何ですか? 2番目の例は機能するのに、最初の例は機能しないのはなぜですか?

ヒント:次の作業に気づきました(まだ完全には理解していません):

user=> (def ^:dynamic y 5)
#'user/y
user=> (defn f [] (defn g [] (println y)) (defn h [] (binding [y 3] (g))) (h))
#'user/f
user=> (f)
3
nil
user=> 
26
Kevin

Clojure 1.4で最初の例を実行すると、結果として3が得られます(予想どおり)。新しいREPLでこれを試しましたか?

^:dynamicは、シンボル(defで定義)が動的に(bindingで)リバウンドされることを意図しているというClojureコンパイラーへの命令です。

例:

(def foo 1)
(binding [foo 2] foo)
=> IllegalStateException Can't dynamically bind non-dynamic var: ...

(def ^:dynamic bar 10)
(binding [bar 20] bar)    ;; dynamically bind bar within the scope of the binding
=> 20
bar                       ;; check underlying value of bar (outside the binding)
=> 10

bindingは呼び出し元のスレッド内で動的スコープを持っていることに注意してください-バインディング内で呼び出された関数はbar(20)の変更された値を参照しますが、他のスレッドは変更されていないルート値を参照します10.10。

最後に、役立つと思われるいくつかのスタイルポイント:

  • defdefnを関数内に配置すると、それを囲む名前空間に影響するため、一般的には悪い考えと見なされます。関数内では、代わりに(let [foo bar] ...)を使用する必要があります。
  • bindingを使用したい場合は、通常、代わりに高階関数を使用して同じ結果を達成できるかどうかを検討する必要があります。 bindingは一部のコンテキストで役立ちますが、一般にパラメーターを渡すのに適した方法ではありません。通常、長期的には関数の合成の方が優れています。これは、bindingが関数の実行に必要な暗黙のコンテキストを作成し、これをテスト/デバッグするのが難しい場合があるためです。
31
mikera