Clojure 环路及;test.check中的状态管理

Clojure 环路及;test.check中的状态管理,clojure,test.check,Clojure,Test.check,随着Spec的介绍,我尝试为我的所有函数编写test.check生成器。这对于简单的数据结构很好,但是对于具有相互依赖的部分的数据结构,这往往变得很困难。换句话说,发电机内部需要进行一些状态管理 拥有Clojure循环/重现或减少的生成器等价物已经非常有帮助,这样一个迭代中产生的值可以存储在某个聚合值中,然后在后续迭代中可以访问该聚合值 需要这样做的一个简单示例是,编写一个生成器,将集合精确地拆分为X个分区,每个分区具有0到Y个元素,然后将这些元素随机分配给任何分区。(请注意,test.chuc

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

拥有Clojure循环/重现或减少的生成器等价物已经非常有帮助,这样一个迭代中产生的值可以存储在某个聚合值中,然后在后续迭代中可以访问该聚合值

需要这样做的一个简单示例是,编写一个生成器,将集合精确地拆分为X个分区,每个分区具有0到Y个元素,然后将这些元素随机分配给任何分区。(请注意,
test.chuck
partition
函数不允许指定X或Y)

如果您通过循环集合来编写这个生成器,那么这将需要访问在以前的迭代中填充的分区,以避免超过Y

有人有什么想法吗?我找到了部分解决方案:

  • test.check的
    绑定
    允许您生成一个值,然后在以后重用该值,但它们不允许迭代

  • 您可以使用
    tuple
    bind
    函数的组合来迭代之前生成的值的集合,但这些迭代无法访问之前迭代期间生成的值

    (defn绑定每个[k coll](应用tcg/tuple(map(fn[x](tcg/bind(tcg/return x)k))coll))

  • 您可以使用原子(或挥发物)来存储和访问在以前的迭代过程中生成的值。这是可行的,但非常不可行,特别是因为您需要在返回生成器之前重置原子/挥发物,以避免其内容在生成器的下一次调用中被重用

  • 生成器之所以像monad,是因为它们的
    bind
    return
    函数暗示了monad库(如Cats)与State monad的结合使用。但是,State monad在Cats 2.0中被删除(因为据说它不适合Clojure),而我所知道的其他支持库没有正式的Clojurescript支持。此外,当在自己的库中实现状态monad时,Clojure的monad专家之一Jim Duey似乎警告说,状态monad的使用与测试不兼容。检查正在缩小(见下表),这大大降低了使用test.check的优点


您可以通过将
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'))))
然而,如果可能的话,值得尝试避免这种模式,因为每次使用
/
绑定
都会破坏收缩过程。有时可以使用
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))))))

谢谢!(我现在想知道为什么递归的想法没有自然地出现在我的脑海中……)你能不能在我的文章(承认仍然有限)中进一步阐述你的“破坏收缩过程”使用包含许多let/bind的生成器进行测试时,我似乎对所发生的收缩感到满意。您知道更可能破坏收缩的任何具体情况吗?这里描述了这个问题:我还应该澄清,如果您有多个子句,那么
gen/let
只会导致真正的
gen/bind
或者如果主体本身是一个发电机。否则它相当于
gen/fmap