循环和test.check中的状态管理

时间:2016-10-31 07:22:01

标签: clojure test.check

随着Spec的引入,我尝试为我的所有函数编写test.check生成器。这对于简单的数据结构来说很好,但是对于具有彼此依赖的部分的数据结构而言往往变得困难。换句话说,然后需要在发电机内进行一些状态管理。

拥有Clojure循环/重复或减少的生成器等价物已经非常有用,因此在一次迭代中生成的值可以存储在某个聚合值中,然后可以在后续迭代中访问。

  

需要这样做的一个简单示例是编写一个生成器,用于将集合拆分为精确的X分区,每个分区具有零和Y元素,然后将元素随机分配给任何分区。 (请注意,test.chuck' partition函数不允许指定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))

  • 您可以使用原子(或挥发物)来储存和储存访问先前迭代期间生成的值。这是有效的,但非常非Clojure,特别是因为在返回生成器之前需要reset!原子/ volatile,以避免它们的内容在下一次调用生成器时被重用。

  • 由于bindreturn函数,生成器与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的优点。

1 个答案:

答案 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))))))