如何在 FsCheck 中轻松过滤掉受歧视的联合案例?

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

考虑一个受歧视的联盟:

type DU = | Foo of string | Bar of int | Baz of decimal * float | Qux of bool

我想用 FsCheck 创建一个包含 DU 个值的列表,但我希望 none 个值属于 Qux 个案例。

这个谓词已经存在:

let isQux = function Qux _ -> true | _ -> false

第一次尝试

我第一次尝试创建一个没有 Qux 大小写的 DU 值列表是这样的:

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

运行 这似乎会产生堆栈溢出,所以我假设幕后发生的事情是 Arb.from<DU> 调用 DoesNotWork.DU.

第二次尝试

然后我试了这个:

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

同上问题

详细解决方案

这是迄今为止我能想到的最佳解决方案:

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

这可行,但有以下缺点:

是否有更优雅的方式告诉 FsCheck 过滤掉 Qux 个值?

以下应该有效:

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

请注意,我在最后使用了 Gen.listOf - 似乎 FsCheck 无法使用给定的生成器

自行生成列表

而不是 Arb.generate,它尝试为类型使用已注册的实例,这是您要定义的实例,这会导致无限循环,请使用 Arb.Default.Derive()直接到基于反射的生成器。

https://github.com/fscheck/FsCheck/blob/master/src/FsCheck/Arbitrary.fs#L788-788

这是一个常见的错误,我们应该能够在 FsCheck 中立即解决:https://github.com/fscheck/FsCheck/issues/109


OP中的特殊问题可以这样解决:

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