Open alfert opened 3 years ago
The problem here is that the final generator is not the result of a FlatMap. I.e. "n" and "m" are completely independent generators within the struct-generator
I thing the correct way would look something like this:
gen.IntRange(10, 20).FlatMap(func(v interface{}) gopter.Gen {
n := v.(int)
var gen_map = map[string]gopter.Gen{"Fst": gen.Const(n), "Snd": gen.IntRange(2*k, 50) }
return gen.Struct(
reflect.TypeOf(IntPair{}),
gen_map,
)
}
Hope this makes sense
Thanks, that works indeed. Here is the solution a bit reformatted:
genIntPair := func() gopter.Gen {
return gen.IntRange(10, 20).FlatMap(func(v interface{}) gopter.Gen {
k := v.(int)
n := gen.Const(k)
m := gen.IntRange(2*k, 50)
var gen_map = map[string]gopter.Gen{"Fst": n, "Snd": m}
return gen.Struct(
reflect.TypeOf(IntPair{}),
gen_map,
)
},
reflect.TypeOf(int(0)))
}
So the trick is that the first generated integer value must be re-introduced as generator by applying Const
, the trivial generator (akin to return
in a monadic setting).
If you have more dependencies some syntactic sugar would be nice, but this seems to be difficult in Go.
Scalacheck works well because of scala's for-comprehention notation, which is a very nice way to write these map/flatMap cascades. Your example
val myGen = for {
n <- Gen.choose(10,20)
m <- Gen.choose(2*n, 500)
} yield (n,m)
actually expands to something like:
Gen.choose(10, 20).flatMap(n -> Gen.choose(2*n, 500).map(m -> (n,m))
I think you could write it like this in go as well, though you have to be very careful when using external variables in anonymous functions.
I like your FlatMap -> Map approach. This boils down to
genIntPair := func() gopter.Gen {
return gen.IntRange(10, 20).FlatMap(func(v interface{}) gopter.Gen {
k := v.(int)
return gen.IntRange(2*k, 50).Map(func(m int) IntPair {
return IntPair{Fst: k, Snd: m}
})
},
reflect.TypeOf(int(0)))
}
This is still baroque, but way more to the point than the first version. I will update my example PR #80
I am looking for a way to generate values which depend on each other. In particular, I want to recreate the following simple example from ScalaCheck's User Guide (https://github.com/typelevel/scalacheck/blob/main/doc/UserGuide.md#generators), where two integer are generated, the first in the range of
10..20
, the second has a lower bound which is twice as large as the first value:My impression was that the
gen.FlatMap()
should provide the required functionality (in Scala,<-
is a monadic assignment, implemented byFlatMap
), but I failed to find a way to succeed.I defined a simple struct to generate two values which can be fed into the property:
The generator is a straight translation of the Scala code, first generating an integer and then generating a second via accessing the generated value of the first. Both generators are finally stored in the struct generator:
However, it does not work:
Remark: I set the upper bound to
50
instead of500
. The property must still hold, but the generator has a smaller pool to pick suitable values: setting the upper bound to500
often results in a passing property!