随着Spec的引入,我尝试为我的所有函数编写test.check生成器。这对于简单的数据结构来说很好,但是对于具有彼此依赖的部分的数据结构而言往往变得困难。换句话说,然后需要在发电机内进行一些状态管理。
拥有Clojure循环/重复或减少的生成器等价物已经非常有用,因此在一次迭代中生成的值可以存储在某个聚合值中,然后可以在后续迭代中访问。
需要这样做的一个简单示例是编写一个生成器,用于将集合拆分为精确的X分区,每个分区具有零和Y元素,然后将元素随机分配给任何分区。 (请注意,
test.chuck
'partition
函数不允许指定X或Y)。如果通过循环遍历集合来编写此生成器,则需要访问在先前迭代期间填充的分区,以避免超过Y.
有人有什么想法吗?我发现的部分解决方案:
let
和bind
允许您生成一个值,然后在以后重用该值,但它们不允许迭代。您可以使用tuple
和bind
函数的组合迭代先前生成的值的集合,但这些迭代无法访问先前迭代期间生成的值。
(defn bind-each [k coll] (apply tcg/tuple (map (fn [x] (tcg/bind (tcg/return x) k)) coll))
您可以使用原子(或挥发物)来储存和储存访问先前迭代期间生成的值。这是有效的,但非常非Clojure,特别是因为在返回生成器之前需要reset!
原子/ volatile,以避免它们的内容在下一次调用生成器时被重用。
由于bind
和return
函数,生成器与monad类似,这暗示了使用像Cats这样的Monad库和State monad。但是,状态monad在Cats 2.0中删除了(因为它据说不适合Clojure),而我所知道的其他支持库没有正式的Clojurescript支持。此外,在他自己的图书馆中实施国家单子时,Clojure的monad专家之一Jim Duey似乎警告说,使用State monad与test.check的收缩不相容(参见http://www.clojure.net/2015/09/11/Extending-Generative-Testing/)的底部,这显着降低了使用test.check的优点。
答案 0 :(得分: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))))))