F# 如何在FsCheck中轻松筛选出歧视性工会案例?

F# 如何在FsCheck中轻松筛选出歧视性工会案例?,f#,fscheck,F#,Fscheck,以一个受歧视的工会为例: type DU = | Foo of string | Bar of int | Baz of decimal * float | Qux of bool 我想用FsCheck创建一个DU值列表,但我不希望所有值都是Qux大小写 此谓词已存在: let isQux = function Qux _ -> true | _ -> false 第一次尝试 我第一次尝试创建一个DU值列表,但没有Qux案例,是这样的: type DoesNotWork =

以一个受歧视的工会为例:

type DU = | Foo of string | Bar of int | Baz of decimal * float | Qux of bool
我想用FsCheck创建一个
DU
值列表,但我不希望所有值都是
Qux
大小写

此谓词已存在:

let isQux = function Qux _ -> true | _ -> false
第一次尝试

我第一次尝试创建一个
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
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
与上述问题相同

详细解决方案

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

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
不带qux的类型=
静态成员DU()=
[
Arb.generate |>Gen.map Foo
Arb.generate |>Gen.map条
Arb.generate |>Gen.map Baz
]
|>第一将军
|>弗罗姆根
[]
让我们重新开始(DU:DU列表)=
printfn“%-5b:%O”(dus |>List.exists isQux |>not)dus
这是可行的,但有以下缺点:

  • 好像要做很多工作
  • 它没有使用已经可用的
    isQux
    函数,因此它似乎微妙地违反了DRY
  • 它并不真正过滤,而是只生成所需的情况(因此只通过省略过滤)
  • 它不是特别可维护的,因为如果我在
    DU
    中添加第五个案例,我必须记住也要为该案例添加一个
    Gen
有没有更优雅的方法告诉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
type DU=|Foo of string | Bar of int | Baz of decimal*float | Qux of bool
设isQux=函数Qux u->真|->假
设g=Arb.generate |>Gen.suchThat(非Gen.listOf
多斯沃克型=
静态成员DU()=Arb.fromGen
[]
让我们重新开始(DU:DU列表)=
printfn“%-5b:%O”(dus |>List.exists isQux |>not)dus

注意:我在结尾使用了
Gen.listOf
——似乎FsCheck无法使用给定的生成器而不是
Arb.generate
,它尝试将注册实例用于类型,这是您试图定义的实例,这会导致无限循环,请使用
Arb.Default.deriver()
将直接进入基于反射的生成器

这是一个常见的错误,我们应该能够在FsCheck中立即解决:


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

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
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
不带qux的类型=
静态成员DU()=Arb.Default.derivate()|>Arb.filter(not List.exists isQux |>not)DU

好吧,通过创建一个
任意的
而不是一个
任意的
来规避这个问题,这可能解决了我眼前的问题,但似乎并没有解决根本的问题。这难道不是一个问题,我们没有得到DU?+1对Baz的提醒和对Qux的介绍的缩减支持吗即使没有这些,这也是个好问题。