代码之家  ›  专栏  ›  技术社区  ›  Sam Estep

这是瞬态的正确用法吗?

  •  4
  • Sam Estep  · 技术社区  · 7 年前

    在谈话中 "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 功能不正确。但考虑到其余谈话的性质,我很难相信这是不正确的。

    分离快速doseq 正确使用瞬态?为什么?

    1 回复  |  直到 7 年前
        1
  •  7
  •   amalloy    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