Clojure ^:constはどのように機能しますか?

I'm trying to understand what ^:const does in clojure. This is what the dev docs say. http://dev.clojure.org/display/doc/1.3

(def定数   {:pi 3.14   :e 2.71})

     

(def ^:const pi(:pi定数))   (def ^:const e(:定数))

     

マップ内のe:とpiのオーバーヘッドは、コンパイル時に発生します。(:pi定数)と(:e定数)は、親defフォームが評価されたときに評価されます。

これは、シンボル pi にバインドされたvarのメタデータであり、シンボル e にバインドされたvarのメタデータであるため、混乱しています。 varルックアップではありません。

誰かが ^:const がやっていることとその背後にある根拠を説明できますか?これは、巨大な let ブロックを使用するか、(pi)(e)のようなマクロを使用する方法と比べてどうですか?

38

3 答え

マップ・ルックアップについては、この問題を混乱させるだけなので、私にとっては悪い例のように見えます。

もっと現実的な例があります:

(def pi 3.14)
(defn circumference [r] (* 2 pi r))

この場合、円周は、実行時に(Var.getRawRootを呼び出すことによって)円周を呼び出すたびにpiを逆参照するコードにコンパイルされます。

(def ^:const pi 3.14)
(defn circumference [r] (* 2 pi r))

この場合、円周は以下のように書かれたのとまったく同じコードにコンパイルされます:

(defn circumference [r] (* 2 3.14 r))

つまり、Var.getRawRootへの呼び出しはスキップされ、少し時間が節約されます。ここでは、circが最初のバージョンであり、circ2が2番目のバージョンです。

user> (time (dotimes [_ 1e5] (circ 1)))
"Elapsed time: 16.864154 msecs"
user> (time (dotimes [_ 1e5] (circ2 1)))
"Elapsed time: 6.854782 msecs"
61
追加された
これは、次の(def ^:const key-to-num {:one 1:two 2})(def sum(+(1つのkey-to-num)コンパイルされた(def sum(+ 1 2))?
追加された 著者 Robin Heggelund Hansen,
いいえ、^:constはプリミティブな値にしか作用しません。
追加された 著者 noisesmith,

サンプルドキュメントでは、ほとんどの場合、constを使用せずにマップ内で何かを見つけた結果としてvarを def した場合、クラスが読み込まれるときにルックアップが行われることを示しています。 プログラムを実行するたびに費用を払います。(クラスが読み込まれるたびに、すべてのルックアップではありません)。そして、それが読み込まれるたびにvarの値を調べるコストを支払う。

代わりにconstを設定すると、コンパイラはコンパイル時にルックアップを実行してから、単純なJava最終変数を出力し、プログラムをコンパイルするときには一度だけ総計を支払うことになります。

これは、いくつかの作業がコンパイル時に、ロード時に、そして残りがうまくいくという点を示していますが、クラスロード時と実行時のいくつかのvarルックアップでは、基本的には何も検索されないため、時間の

9
追加された
それを指摘してくれてありがとう、私もVARルックアップを追加するように編集しました。
追加された 著者 Arthur Ulfeldt,
私はこれが正確だとは思わない。より重要な節約はmap-lookupではなく、 pi e のvar-lookupであり、 > ^:const が含まれている場合はまったく発生しません。
追加された 著者 amalloy,

上記の効率面に加えて、有用な安全面もある。次のコードを考えてみましょう:

(def two 2)
(defn times2 [x] (* two x))
(assert (= 4 (times2 2)))    ; Expected result

(def two 3)                  ; Ooops! The value of the "constant" changed
(assert (= 6 (times2 2)))    ; Used the new (incorrect) value

(def ^:const const-two 2)
(defn times2 [x] (* const-two x))
(assert (= 4 (times2 2)))    ; Still works

(def const-two 3)            ; No effect!
(assert (= 3 const-two ))    ; It did change...
(assert (= 4 (times2 2)))    ; ...but the function did not.

したがって、varsを定義するときに^:constメタデータを使用することによって、変数は使用されるすべての場所に効果的に "インライン"されます。したがって、その後のvarの変更は、「古い」値がすでにインライン化されているコードには影響しません。

The use of ^:const also serves a documentation function. When one reads (def ^:const pi 3.14159) is tells the reader that the var pi is not ever intended to change, that it is simply a convenient (& hopefully descriptive) name for the value 3.14159.

前述のように、私のコードでは決して ^:const を使用しないことに注意してください。なぜなら、それは不正であり、varが決して変更されないという誤った保証を提供するからです。問題は ^:const はvarを再定義できないことを意味しますが、 const-two を見てもvarが変更されることはありません。代わりに、 const-two はコンパイル時に各使用場所にコピー/インライン化されているので、 ^:const はvarに新しい値があることを隠します。 varが変更される前に(実行時に)

もっと良い解決策は、 ^:const varを変更しようとすると例外をスローすることです。

8
追加された
Clojureスタイルガイドには、「定数に特別な表記法を使用しないでください。特に指定しない限り、すべてが定数とみなされます。これは、値が少なくとも def されていないときには、値が日常的にインライン化されることを示唆していますか? (おそらく ^:const は "この変数の後の def があってもインラインになる可能性がある"ことを意味しますが、 ) ^:const ed varsが異なっていても、なぜ私はそうしてはいけないのですか?それをネーミングによって明示的にするCommon Lispのように + name +
追加された 著者 Mars,
@Mars私は、Clojureでは、Varが設定された後にVarのルート値を変更することをお勧めしないので、スタイルガイドはVarsが一定であると仮定すべきだと考えています。変化するVarは、通常、動的なVarになり、耳たぶに囲まれます。したがって、 name は定数とみなされます(コンパイラの最適化や保証では変更されないため)。 * name * は定数ではないと見なされます。 ^:const は、varの使用をインライン展開してコードを最適化する必要があることをコンパイラに示すためのものです。
追加された 著者 Didier A.,
^:constを使うと2つの効果があります。 (1)は、コンパイラが値をインライン化し、varを使用しないので、varの変更や乱用の影響を受けません。 (2)は単にドキュメンテーションです。著者は変更すべきではない価値があると主張しています。
追加された 著者 Alan Thompson,