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:
- It seems like a lot of work
- It doesn't use the already available
isQux
function, so it seems to subtly violate DRY - It doesn't really filter, but rather only produces the desired cases (so filters only by omission).
- It isn't particularly maintainable, because if I ever add a fifth case to
DU
, I would have to remember to also add aGen
for that case.
Is there a more elegant way to tell FsCheck to filter out Qux
values?