2016-10-31 56 views
3

隨着Spec的推出,我嘗試爲我的所有函數編寫test.check生成器。這對簡單的數據結構來說很好,但對於具有相互依賴的部分的數據結構往往會變得困難。換句話說,發電機內的一些國家管理部門是需要的。test.check中的循環和狀態管理

Clojure循環的生成器等價物/遞歸或減少已經非常有幫助,因此一次迭代中生成的值可以存儲在某個聚合值中,然後在隨後的迭代中可以訪問該聚合值。

一個簡單的例子,其中,這將是必需的,是提供一種用於拆分集合的發生器寫入準確X分區,零和Y的元素,並且其中所述元件然後隨機分配到任何之間具有每個分區的分區。 (請注意,test.chuckpartition函數不允許指定X或Y)。

如果你寫這個發電機通過收集循環,那麼這將需要訪問以前的迭代過程中填補了分區,以避免超過Y.

沒有任何人有什麼想法?部分的解決方案,我發現:

  • test.check的letbind允許您以後在生成一個值,然後重新使用該值,但他們不允許重複。
  • 您可以使用tuplebind函數的組合迭代以前生成的值的集合,但是這些迭代無法訪問先前迭代期間生成的值。

    (defn bind-each [k coll] (apply tcg/tuple (map (fn [x] (tcg/bind (tcg/return x) k)) coll))

  • 可以使用原子(或揮發物)來存儲先前迭代期間產生&訪問值。這是有效的,但是非常不合適,特別是因爲在生成器返回之前你需要原子/易失性reset!,以避免它們的內容在下一次生成器調用時被重用。

  • 由於它們的bindreturn函數,發生器具有類似monad的功能,這暗示了使用monad庫(如Cats)與狀態monad結合使用。然而,在Cats 2.0中刪除了State monad(因爲據說它不適合Clojure),而我知道的其他支持庫沒有正式的Clojurescript支持。此外,在他自己的圖書館中實施國家monad時,Clojure的monad專家之一Jim Duey似乎警告說,使用State monad與test.check的收縮不兼容(參見http://www.clojure.net/2015/09/11/Extending-Generative-Testing/的底部),這會顯着降低使用test.check的優點。

回答

2

你可以完成你用明確的遞歸結合gen/let(或等價gen/bind)描述迭代:

(defn make-foo-generator 
    [state] 
    (if (good-enough? state) 
    (gen/return state) 
    (gen/let [state' (gen-next-step state)] 
     (make-foo-generator state')))) 

然而,這是值得努力避免這種模式如果可能的話,因爲每次使用let/bind都會破壞收縮過程。有時可以使用gen/fmap重新組織生成器。例如,要將一個集合分成一系列X子集(我認識到這不完全是你的例子,但我認爲它可以調整以適合),你可以這樣做:

(defn partition 
    [coll subset-count] 
    (gen/let [idxs (gen/vector (gen/choose 0 (dec subset-count)) 
          (count coll))] 
    (->> (map vector coll idxs) 
     (group-by second) 
     (sort-by key) 
     (map (fn [[_ pairs]] (map first pairs)))))) 
+0

謝謝! (我現在想知道爲什麼遞歸的想法不是自然而然地發生在我身上......)你能否擴展你的「破壞縮小的過程」?在我的(當然還是有限的)測試中,包含許多讓/綁定的生成器,我似乎對發生的收縮感到高興。你是否知道任何更可能破壞收縮的具體情況? –

+0

這裏描述的問題:http://dev.clojure.org/jira/browse/TCHECK-112 – gfredericks

+0

我還應該澄清,如果你有多個子句,'gen/let'只會導致一個真正的'gen/bind'或者如果身體本身就是發電機。否則它等同於'gen/fmap'。 – gfredericks