2016-10-31 54 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