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

Mar*_*ann 8 f# fscheck

考虑一个被歧视的联盟:

type DU = | Foo of string | Bar of int | Baz of decimal * float | Qux of bool
Run Code Online (Sandbox Code Playgroud)

我想DU用FsCheck 创建一个值列表,但我不希望这些值都是Qux这样的.

这个谓词已经存在:

let isQux = function Qux _ -> true | _ -> false
Run Code Online (Sandbox Code Playgroud)

第一次尝试

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

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
Run Code Online (Sandbox Code Playgroud)

运行这似乎会产生堆栈溢出,所以我假设在场景后面发生的是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
Run Code Online (Sandbox Code Playgroud)

与上述问题相同.

详细的解决方案

这是迄今为止我能够提出的最佳解决方案:

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
Run Code Online (Sandbox Code Playgroud)

这有效,但有以下缺点:

  • 这似乎很多工作
  • 它不使用已经可用的isQux功能,因此它似乎巧妙地违反DRY
  • 它并不真正过滤,而是仅产生所需的情况(因此仅通过省略过滤).
  • 它不是特别可维护,因为如果我添加第五个案例DU,我将不得不记得Gen为该案例添加一个.

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

Kur*_*out 7

而不是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
Run Code Online (Sandbox Code Playgroud)


the*_*ski 6

下面应该工作:

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
Run Code Online (Sandbox Code Playgroud)

注意我Gen.listOf在最后使用 - 似乎 FsCheck 无法使用给定的生成器生成自己的列表