我正在努力了解^: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)
这样的宏相比,这相比如何?
答案 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时抛出异常。