Clojure ^:const是如何工作的?

bmi*_*are 41 constants clojure

我试图理解^:constclojure中的内容.这就是开发人员所说的.http://dev.clojure.org/display/doc/1.3

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

(def ^:const pi(:pi constants))(def ^:const e(:e constants))

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

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

有人可以解释一下^:const正在做什么以及使用它背后的理由吗?请问这个比较使用一个巨大的let块或使用宏象(pi)(e)

Chr*_*ins 71

这对我来说似乎是一个不好的例子,因为关于地图查找的东西只是混淆了这个问题.

一个更现实的例子是:

(def pi 3.14)
(defn circumference [r] (* 2 pi r))
Run Code Online (Sandbox Code Playgroud)

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

(def ^:const pi 3.14)
(defn circumference [r] (* 2 pi r))
Run Code Online (Sandbox Code Playgroud)

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

(defn circumference [r] (* 2 3.14 r))
Run Code Online (Sandbox Code Playgroud)

也就是说,跳过了对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"
Run Code Online (Sandbox Code Playgroud)


Art*_*ldt 10

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

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

这是一个人为的例子,因为在类加载时的一次映射查找和运行时的一些var查找基本上都没有,尽管它说明了一些工作在编译时发生,一些在加载时发生,其余部分正好...其余的的时间

  • 我不认为这是准确的.更重要的节省不是map-lookup,而是`pi`和`e`的var-lookup,如果缺少`^:const`,每次引用其中任何一个时都会出现,但不会出现在所有当包含`^:const`时. (2认同)

Ala*_*son 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.
Run Code Online (Sandbox Code Playgroud)

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

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

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

一个更好的解决方案是在尝试更改^:constvar 时抛出异常.

  • [Clojure样式指南](https://github.com/bbatsov/clojure-style-guide#naming)说:“不要对常量使用特殊的表示法;除非另有说明,否则所有内容均假定为常量。” 这是否意味着至少在不重新定义值的情况下,常规地内联值?(也许`^:const`的意思仅仅是“即使该变量有一个更高的`def`,也可以内联”,但是如果没有更高版本的`def`,即使是非`^:const`ed的var也会得到内联。或如果`^:const`ed的var不同,为什么我不应该通过命名来使它明确,例如Common Lisp中的`+ name +`。 (2认同)