Mark Seemann
Mark Seemann

Reputation: 233150

How to easily filter out a discriminated union case in FsCheck?

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:

Is there a more elegant way to tell FsCheck to filter out Qux values?

Upvotes: 9

Views: 926

Answers (2)

Kurt Schelfthout
Kurt Schelfthout

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

theimowski
theimowski

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

Related Questions