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