Clojure ^:const是如何工作的?

时间:2012-02-06 15:33:39

标签: clojure constants

我正在努力了解^:const在clojure中的作用。这就是开发人员所说的。 http://dev.clojure.org/display/doc/1.3

  

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

     

(def ^:const pi(:pi常数))   (def ^:const e(:e常量))

     

查找的开销:e和:地图中的pi发生在编译时,因为(:pi常量)和(:e常量)在评估其父def形式时进行评估。

这是令人困惑的,因为元数据是针对符号pi的var绑定,而var绑定到符号e,但下面的句子说它有助于加速地图查找,而不是var查找。

有人可以解释^:const正在做什么以及使用它的理由吗?与使用巨型let块或使用像(pi)(e)这样的宏相比,这相比如何?

3 个答案:

答案 0 :(得分:66)

这对我来说似乎是一个不好的例子,因为关于地图查找的内容只会让问题混淆。

更现实的例子是:

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

在这种情况下,圆周体被编译成代码,在运行时解析pi(通过调用Var.getRawRoot),每次调用圆周。

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

在这种情况下,将圆周编译成完全相同的代码,就好像它是这样写的:

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

也就是说,跳过了对Var.getRawRoot的调用,这节省了一点时间。这是一个快速测量,其中circ是上面的第一个版本,circ2是第二个版本:

user> (time (dotimes [_ 1e5] (circ 1)))
"Elapsed time: 16.864154 msecs"
user> (time (dotimes [_ 1e5] (circ2 1)))
"Elapsed time: 6.854782 msecs"

答案 1 :(得分:10)

在示例文档中,他们试图表明,在大多数情况下,如果def var是在不使用const的情况下在地图中查找某些内容的结果,则在类加载时将进行查找。 因此,每次运行程序时都需要支付一次费用(不是每次查找,只是在加载类时)。并且每次读取时都要花费在var中查找值的费用。

如果您改为使用const,则编译器将在编译时执行查找,然后发出一个简单的java final变量,并且在编译时,总共只需支付一次查找费用程序

这是一个人为的例子,因为在类加载时进行一次映射查找,在运行时进行一些var查找基本上没什么,尽管它说明了某些工作在编译时发生,有些工作在加载时发生,其余工作正常。其余的时间

答案 2 :(得分:8)

除了上述效率方面之外,还有一个安全方面也是有用的。请考虑以下代码:

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

因此,通过在定义变量时使用^:const元数据,变量可以有效地“内联”到它们所使用的每个位置。因此,对var的任何后续更改都不会影响已经内联“旧”值的任何代码。

^:const的使用也提供了文档功能。当读取(def ^:const pi 3.14159)告诉读者var pi不打算改变时,它只是值3.14159的一个方便的(并且有希望描述性的)名称。

说完上述内容后,请注意我从未在我的代码中使用^:const,因为它具有欺骗性,并且提供了“错误保证”,即var永远不会改变。问题是^:const意味着无法重新定义var,但正如我们在const-two中看到的那样,它不会阻止var被更改。相反,^:const隐藏了var具有新值的事实,因为const-two已在var更改之前(在运行时)被复制/内联(在编译时)到每个使用位置)。

更好的解决方案是在尝试更改^:const var时抛出异常。