2017-07-14 136 views
4

在談話"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)])) 

(這些都是逐字複製,包括在最後unidiomatic縮進)

他指出,在他使用的基準測試中,上面的第一個函數花了1.1秒,而上面的第二個函數花了0.8秒,注意到第二個函數因此優於第一個函數。然而,根據Clojure documentation on transients

特別要注意,暫態不是設計在現場。您必須在下次調用中捕獲並使用返回值。

因此,在我看來,這個separate-fast-doseq函數是不正確的。但考慮到談話其餘部分的性質,我很難相信這是不正確的。

這是separate-fast-doseq功能瞬變的正確使用?爲什麼或者爲什麼不?(如果不是,那是什麼突破的例子嗎?)

回答

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 
+0

聽起來好像是:不這樣做,它只是碰巧工作,能夠隨時休息。 「但它在我的機器上工作」,從未走得太遠。 –