0

Using an example from Clojure's test.check let generator, generate a non-empty list of strings, give that list to another generator to pick a string from, then create a map that contains the string list and the selected string. In Clojure, it looks as follows:

(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})

Taking io.kotest.property.arbitrary.bind for inspiration, I've tried implementing it as follows, but it doesn't work (Kotlin compiler spitted out "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)
        }
    }
}
shaolang
  • 995
  • 11
  • 25

2 Answers2

2

Turns out dropping bindFn parameter solves the problem, but the solution looks a little ugly as it needs to return a 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)
        }
    }
}

Then with the above, using it looks as follows:

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
        }
    }
})
shaolang
  • 995
  • 11
  • 25
0

Inspire from above solution and wrote a shorter one

    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
        }
    }
})
Chi Dov
  • 1,447
  • 1
  • 11
  • 22