这是瞬变的正确用法吗?
Is this a correct usage of transients?
在演讲 "Bootstrapping Clojure at Groupon" by Tyler Jennings 中,从 25:14 到 28:24,他讨论了 separate
函数的两个实现,都使用瞬态:
(defn separate-fast-recur [pred coll]
(loop [true-elements (transient [])
false-elements (transient [])
my-coll coll]
(if (not (empty? my-coll))
(let [curr (first my-coll)
tail (rest my-coll)]
(if (pred curr)
(recur (conj! true-elements curr) false-elements tail)
(recur true-elements (conj! false-elements curr) tail)))
[(persistent! true-elements) (persistent! false-elements)])))
(defn separate-fast-doseq [pred coll]
(let [true-elements (transient [])
false-elements (transient [])]
(doseq [curr coll]
(if (pred curr)
(conj! true-elements curr)
(conj! false-elements curr)))
[(persistent! true-elements) (persistent! false-elements)]))
(这些都是逐字复制的,包括第二行最后一行的单行缩进。)
他指出,在他使用的基准测试中,上面的第一个函数花费了 1.1 秒,而上面的第二个函数花费了 0.8 秒,并指出第二个因此优于第一个。但是,根据 Clojure documentation on transients:
Note in particular that transients are not designed to be bashed in-place. You must capture and use the return value in the next call.
因此在我看来,这个 separate-fast-doseq
函数是不正确的。但考虑到接下来谈话的性质,我很难相信这是不正确的。
这个 separate-fast-doseq
函数是否正确使用瞬态?为什么或为什么不?(如果没有,它破坏的例子是什么?)
由于您怀疑的原因,第二种实施方式不正确。 允许 瞬态集合为了效率而改变自身,但从不要求,因此这些 conj!
调用中的任何一个都可以return 具有不同身份的对象。如果发生这种情况,则通过丢弃 conj!
的结果,您粘贴的函数将无法正常运行。
但是,我无法提供它崩溃的例子。在 current implementation of Clojure 中,碰巧 conj!
总是在原地变异。请注意末尾的无条件 return this
。因此,此函数将按预期运行。但是,它的正确性依赖于可能随时更改的实现细节。
有关确实中断的类似操作的示例,请尝试使用映射而不是向量:
(let [m (transient {})]
(doseq [i (range 20)]
(assoc! m i i))
(count (persistent! m)))
8
在演讲 "Bootstrapping Clojure at Groupon" by Tyler Jennings 中,从 25:14 到 28:24,他讨论了 separate
函数的两个实现,都使用瞬态:
(defn separate-fast-recur [pred coll]
(loop [true-elements (transient [])
false-elements (transient [])
my-coll coll]
(if (not (empty? my-coll))
(let [curr (first my-coll)
tail (rest my-coll)]
(if (pred curr)
(recur (conj! true-elements curr) false-elements tail)
(recur true-elements (conj! false-elements curr) tail)))
[(persistent! true-elements) (persistent! false-elements)])))
(defn separate-fast-doseq [pred coll]
(let [true-elements (transient [])
false-elements (transient [])]
(doseq [curr coll]
(if (pred curr)
(conj! true-elements curr)
(conj! false-elements curr)))
[(persistent! true-elements) (persistent! false-elements)]))
(这些都是逐字复制的,包括第二行最后一行的单行缩进。)
他指出,在他使用的基准测试中,上面的第一个函数花费了 1.1 秒,而上面的第二个函数花费了 0.8 秒,并指出第二个因此优于第一个。但是,根据 Clojure documentation on transients:
Note in particular that transients are not designed to be bashed in-place. You must capture and use the return value in the next call.
因此在我看来,这个 separate-fast-doseq
函数是不正确的。但考虑到接下来谈话的性质,我很难相信这是不正确的。
这个 separate-fast-doseq
函数是否正确使用瞬态?为什么或为什么不?(如果没有,它破坏的例子是什么?)
由于您怀疑的原因,第二种实施方式不正确。 允许 瞬态集合为了效率而改变自身,但从不要求,因此这些 conj!
调用中的任何一个都可以return 具有不同身份的对象。如果发生这种情况,则通过丢弃 conj!
的结果,您粘贴的函数将无法正常运行。
但是,我无法提供它崩溃的例子。在 current implementation of Clojure 中,碰巧 conj!
总是在原地变异。请注意末尾的无条件 return this
。因此,此函数将按预期运行。但是,它的正确性依赖于可能随时更改的实现细节。
有关确实中断的类似操作的示例,请尝试使用映射而不是向量:
(let [m (transient {})]
(doseq [i (range 20)]
(assoc! m i i))
(count (persistent! m)))
8