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?
Discriminated unions are useful for heterogeneous data; data that can have special cases, including valid and error cases; data that varies in type from one instance to another; and as an alternative for small object hierarchies. In addition, recursive discriminated unions are used to represent tree data structures.
A discriminated union is a union data structure that holds various objects, with one of the objects identified directly by a discriminant. The discriminant is the first item to be serialized or deserialized. A discriminated union includes both a discriminant and a component.
For example, consider the following declaration of a Shape type. The preceding code declares a discriminated union Shape, which can have values of any of three cases: Rectangle, Circle, and Prism. Each case has a different set of fields. The Rectangle case has two named fields, both of type float, that have the names width and length.
Normally, the case identifiers can be used without qualifying them with the name of the union. If you want the name to always be qualified with the name of the union, you can apply the RequireQualifiedAccess attribute to the union type definition. In F# Discriminated Unions are often used in domain-modeling for wrapping a single type.
Discriminated unions are similar to union types in other languages, but there are differences. As with a union type in C++ or a variant type in Visual Basic, the data stored in the value is not fixed; it can be one of several distinct options. Unlike unions in these other languages, however, each of the possible options is given a case identifier.
Accessibility for discriminated unions defaults to public. For example, consider the following declaration of a Shape type. The preceding code declares a discriminated union Shape, which can have values of any of three cases: Rectangle, Circle, and Prism. Each case has a different set of fields.
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With