Comment Clojure ^:const fonctionne-t-il?

J'essaie de comprendre ce que ^:const fait dans clojure. C'est ce que disent les dev docs. http://dev.clojure.org/display/doc/1.3

(constantes def {: pi 3.14 :e 2.71})

(def ^:const pi (:pi constantes)) (def ^: const E (: E constantes))

La surcharge de recherche de :e et: pi dans la carte se produit au moment de la compilation, car (:Pi constantes) et (:e constantes) sont évaluées lorsque leurs formes Def parentes sont évaluées.

C'est déroutant puisque les métadonnées sont pour le Var lié au symbole pi, et le var lié au symbole e, mais la phrase ci-dessous dit qu'elle aide à accélérer les recherches de carte, pas les recherches var.

Quelqu'un peut-il expliquer ce que ^:const fait et la raison d'être de son utilisation? Comment cela se compare à l'aide d'un géant let bloc ou à l'aide d'une macro comme (pi) et (e)?

38
demandé sur bmillare 2012-02-06 19:33:39

3 réponses

Cela me semble être un mauvais exemple, puisque les choses sur la recherche de carte ne font que confondre le problème.

Un exemple plus réaliste serait:

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

Dans ce cas, le corps de circonférence est compilé en code qui déréférente pi à l'exécution (en appelant Var.getRawRoot), chaque fois que la circonférence est appelée.

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

Dans ce cas, la circonférence est compilée dans exactement le même code que si elle avait été écrite comme ceci:

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

, ce Qui est, l'appel à Var.getRawRoot est ignoré, ce qui permet d'économiser un peu de temps. Voici une mesure rapide, où circ est la première version ci-dessus, et circ2 est la seconde:

user> (time (dotimes [_ 1e5] (circ 1)))
"Elapsed time: 16.864154 msecs"
user> (time (dotimes [_ 1e5] (circ2 1)))
"Elapsed time: 6.854782 msecs"
62
répondu Chris Perkins 2012-02-06 19:28:42

Dans les exemples de documents, ils essaient de montrer que dans la plupart des cas, si vous def un var est le résultat de la recherche de quelque chose dans une carte sans utiliser const, la recherche se produira lorsque la classe se charge. donc, vous payez le coût une fois chaque fois que vous exécutez le programme (pas à chaque recherche, juste au moment où la classe se charge). Et payer le coût de la recherche de la valeur dans le var chaque fois qu'il est lu.

Si vous le faites à la place const, le compilateur préforme la recherche au moment de la compilation et puis émettez une simple variable finale java et vous ne paierez le coût de recherche qu'une seule fois total au moment où vous compilez le programme.

Ceci est un exemple artificiel car une recherche de carte au moment du chargement de la classe et certaines recherches var à l'exécution ne sont fondamentalement rien, bien qu'elle illustre le fait que certains travaux se produisent au moment de la compilation, d'autres au moment du chargement, et le reste bien ... le reste du temps

9
répondu Arthur Ulfeldt 2012-02-06 22:03:02

Outre l'aspect efficacité décrit ci-dessus, il y a un aspect sécurité qui est également utile. Considérez le code suivant:

(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.

Ainsi, en utilisant les métadonnées ^:const lors de la définition de vars, les vars sont effectivement "inline" dans chaque endroit où ils sont utilisés. Par conséquent, toute modification ultérieure de la var n'affecte aucun code dans lequel la valeur "ancienne" a déjà été insérée.

L'utilisation de^: const sert également une fonction de documentation. Quand on lit (def ^: const pi 3.14159) est indique au lecteur que le var pi n'est jamais destiné à changer, qu'il s'agit simplement d'un nom pratique (et, espérons-le, descriptif) pour la valeur 3.14159.

Ayant dit ce qui précède, notez que je n'utilise jamais ^:const dans mon code, car il est trompeur et fournit une "fausse assurance" qu'un var ne changera jamais. Le problème est que ^:const implique que l'on ne peut pas redéfinir une var, mais comme nous l'avons vu avec const-two, il n'empêche pas la var d'être changé. Au lieu de cela, ^:const cache le fait que le var a un nouveau valeur, puisque const-two a été copié/inline (au moment de la compilation) à chaque lieu d'utilisation avant que la var ne soit modifiée (au moment de l'exécution).

Une bien meilleure solution serait de lancer une Exception en essayant de changer un ^:const var.

8
répondu Alan Thompson 2017-01-05 17:26:55