Reputation: 233150
Consider a Discriminated Union:
type DU = | Foo of string | Bar of int | Baz of decimal * float | Qux of bool
I'd like to create a list of DU
values with FsCheck, but I want none of the values to be of the Qux
case.
This predicate already exists:
let isQux = function Qux _ -> true | _ -> false
First attempt
My first attempt to create a list of DU
values without the Qux
case was something like this:
type DoesNotWork =
static member DU () = Arb.from<DU> |> Arb.filter (not << isQux)
[<Property(MaxTest = 10 , Arbitrary = [| typeof<DoesNotWork> |])>]
let repro (dus : DU list) =
printfn "%-5b : %O" (dus |> List.exists isQux |> not) dus
Running this seems to produce a stack overflow, so I assume that what happens behind the scene is that Arb.from<DU>
calls DoesNotWork.DU
.
Second attempt
Then I tried this:
type DoesNotWorkEither =
static member DU () =
Arb.generate<DU>
|> Gen.suchThat (not << isQux)
|> Arb.fromGen
[<Property(MaxTest = 10 , Arbitrary = [| typeof<DoesNotWorkEither> |])>]
let repro (dus : DU list) =
printfn "%-5b : %O" (dus |> List.exists isQux |> not) dus
Same problem as above.
Verbose solution
This is the best solution I've been able to come up with so far:
type WithoutQux =
static member DU () =
[
Arb.generate<string> |> Gen.map Foo
Arb.generate<int> |> Gen.map Bar
Arb.generate<decimal * float> |> Gen.map Baz
]
|> Gen.oneof
|> Arb.fromGen
[<Property(MaxTest = 10 , Arbitrary = [| typeof<WithoutQux> |])>]
let repro (dus : DU list) =
printfn "%-5b : %O" (dus |> List.exists isQux |> not) dus
This works, but has the following disadvantages:
isQux
function, so it seems to subtly violate DRYDU
, I would have to remember to also add a Gen
for that case.Is there a more elegant way to tell FsCheck to filter out Qux
values?
Upvotes: 9
Views: 926
Reputation: 8990
Instead of Arb.generate
, which tries to use the registered instance for the type, which is the instance you're trying to define, which causes an infinite loop, use Arb.Default.Derive()
which will go straight to the reflective based generator.
https://github.com/fscheck/FsCheck/blob/master/src/FsCheck/Arbitrary.fs#L788-788
This is such a common mistake we should be able to solve out of the box in FsCheck: https://github.com/fscheck/FsCheck/issues/109
The particular problem in the OP can be solved like this:
type WithoutQux =
static member DU () = Arb.Default.Derive () |> Arb.filter (not << isQux)
[<Property(MaxTest = 10 , Arbitrary = [| typeof<WithoutQux> |])>]
let repro (dus : DU list) =
printfn "%-5b : %O" (dus |> List.exists isQux |> not) dus
Upvotes: 8
Reputation: 447
The below should work:
type DU = | Foo of string | Bar of int | Baz of decimal * float | Qux of bool
let isQux = function Qux _ -> true | _ -> false
let g = Arb.generate<DU> |> Gen.suchThat (not << isQux) |> Gen.listOf
type DoesWork =
static member DU () = Arb.fromGen g
[<Property(MaxTest = 10 , Arbitrary = [| typeof<DoesWork> |])>]
let repro (dus : DU list) =
printfn "%-5b : %O" (dus |> List.exists isQux |> not) dus
Note I used Gen.listOf
at the end - seems like FsCheck fails to generate itself a list with the given generator
Upvotes: 6