【问题标题】:How to easily filter out a discriminated union case in FsCheck?如何在 FsCheck 中轻松过滤出歧视性联合案例?
【发布时间】:2015-06-26 08:30:48
【问题描述】:

考虑一个有区别的联盟:

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

我想使用 FsCheck 创建一个 DU 值列表,但我不希望任何值都属于 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&lt;DU&gt; 调用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

这可行,但有以下缺点:

  • 看来工作量很大
  • 它没有使用已经可用的isQux函数,所以它似乎巧妙地违反了DRY
  • 它并没有真正过滤,而是只产生所需的情况(因此仅通过省略进行过滤)。
  • 它不是特别可维护,因为如果我向DU 添加第五个案例,我将不得不记住还为那个案例添加一个Gen。

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

【问题讨论】:

  • +1 用于提醒有关 Baz 和 Qux 的介绍。尽管即使没有这些,这也是一个很好的问题。

标签: f# fscheck


【解决方案1】:

Arb.generate 尝试使用已注册的类型实例,这是您尝试定义的实例,这会导致无限循环,而不是 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

【讨论】:

    【解决方案2】:

    以下应该可以工作:

    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 无法使用给定的生成器生成自己的列表

    【讨论】:

    • 嗯,通过创建Arbitrary&lt;DU list&gt; 而不是Arbitrary&lt;DU&gt; 来规避问题,这可能会解决我眼前的问题,但似乎并没有解决根本问题。
    • 这不会造成我们没有得到对 DU 的支持减少的问题吗?
    猜你喜欢
    • 1970-01-01
    • 2011-02-09
    • 2016-03-22
    • 1970-01-01
    • 1970-01-01
    • 2018-10-20
    • 2016-08-01
    • 2012-06-14
    • 2019-11-29
    相关资源
    最近更新 更多