如何在 Kotest 的另一台发电机中使用一台发电机的输出?

How to use output from one generator in another generator in Kotest?

使用 Clojure test.check let generator 中的示例,生成一个非空字符串列表,将该列表提供给另一个生成器以从中选择一个字符串,然后创建一个包含该字符串列表和所选字符串的映射.在 Clojure 中,它看起来如下:

(gen/let [list-of-strings (gen/not-empty (gen/list gen/string))
          a-string        (gen/element list-of-strings)]   ;; use the generated list-of-strings above
  {:all-strings list-of-strings
   :selected    a-string})

io.kotest.property.arbitrary.bind启发,我试过如下实现,但是不行(Kotlin编译器吐出来"Type inference failed"):

fun <A, B, T: Any> let(genA: Gen<A>, genB: (A) -> Gen<B>, bindFn: (A, B) -> T): Arb<T> {
    return arb { rs ->
        val iterA = genA.generate(rs).iterator()

        generateSequence {
            val a = iterA.next()
            val iterB = genB(a.value).generate(rs).iterator()
            val b = iterB.next()
            bindFn(a.value, b.value)
        }
    }
}

原来删除 bindFn 参数解决了问题,但解决方案看起来有点难看,因为它需要 return 一个 Pair:

fun <A, B> let(genA: Gen<A>, genBFn: (A) -> Gen<B>): Arb<Pair<A, B>> {
    return arb { rs ->
        val iterA = genA.generate(rs).iterator()

        generateSequence {
            val a = iterA.next().value

            // could combine the following to one line, but split for clarity
            val genB = genBFn(a)
            val iterB = genB.generate(rs).iterator()
            Pair(a, iterB.next().value)
        }
    }
}

然后结合上面,使用起来如下:

class StringTest : StringSpec({
    "element is in list" {
        val letGen = let(
            Arb.list(Arb.string(), range=1..100),    // genA
            { xs -> Arb.element(xs) }                // genBFn
        )

        forAll(letGen) { (xs, x) ->
            x in xs
        }
    }
})

从上面的解决方案中得到启发,写了一个更短的解决方案

    fun <A, B> Gen<A>.then(genFn: (A) -> Gen<B>): Arb<Pair<A, B>> =
        arbitrary { rs ->
            val first = this.generate(rs).first().value
            val second = genFn(first).generate(rs).first().value
            Pair(first, second)
        }
class StringTest : StringSpec({
    "element is in list" {
        val dependArb = 
            Arb.list(Arb.string(), range=1..100).then { Arb.element(it) } // genBFn

        forAll(dependArb) { (xs, x) ->
            x in xs
        }
    }
})