这是瞬态的正确用法吗?

Sam*_*tep 4 loops clojure mutable dynamic-arrays data-structures

Tyler Jennings的演讲"Groupon的引导Clojure"中,从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)]))
Run Code Online (Sandbox Code Playgroud)

(这些都是逐字复制的,包括第二行最后一行的单一缩进.)

他指出,在他使用的基准测试中,上面的第一个函数需要1.1秒,而上面的第二个函数需要0.8秒,因此注意第二个函数优于第一个函数.但是,根据关于瞬态Clojure文档:

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

因此在我看来这个separate-fast-doseq功能是不正确的.但考虑到其他话题的性质,我很难相信这是不正确的.

separate-fast-doseq功能是否正确使用瞬态?为什么或者为什么不?(如果没有,它的一个例子是什么?)

ama*_*loy 7

第二个实现是错误的,因为您怀疑的原因.瞬态集合允许变异以提高效率,但它永远不需要,因此这些conj!调用中的任何一个都可能返回具有不同标识的对象.如果发生这种情况,那么通过丢弃结果conj!,您粘贴的功能将表现不正确.

但是,我无法提供它打破的例子.在Clojure当前实现中,conj! 确实发生了变化.注意最后的无条件return this.因此,此函数将按预期运行.但是,它依赖于实现细节的正确性,这些细节可能随时发生变化.

有关类似操作的示例,请尝试使用地图而不是向量:

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

8
Run Code Online (Sandbox Code Playgroud)