这是瞬态的正确用法吗?

时间:2017-07-14 23:49:41

标签: loops data-structures clojure dynamic-arrays mutable

在演讲"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

  

特别注意,瞬态并非设计为就地瞄准。您必须在下次调用中捕获并使用返回值。

因此在我看来这个separate-fast-doseq函数是不正确的。但考虑到其余话题的性质,我很难相信这是不正确的。

separate-fast-doseq功能是否正确使用了瞬变?为什么或为什么不呢?(如果不是,它的例子是什么?)

1 个答案:

答案 0 :(得分:7)

第二种实现方式不正确,因为您怀疑的原因。一个瞬态集合允许为了提高效率而改变自身,但它永远不会需要,因此这些conj!调用中的任何一个都可能返回一个不同的对象身份。如果发生这种情况,那么通过放弃conj!的结果,您粘贴的功能将表现不正确。

但是,我不能提供一个破坏的例子。在current implementation of Clojure中,conj! 确实总是在适当的位置发生变异。请注意最后的无条件return this。因此,此函数将按预期运行。但是,它依赖于实现细节的正确性,这些细节可能随时发生变化。

对于类似操作的示例,请尝试使用地图而不是矢量:

(let [m (transient {})]
  (doseq [i (range 20)]
    (assoc! m i i))
  (count (persistent! m)))

8