Как легко отфильтровать дискриминированный союз в FsCheck?

Рассмотрим дискриминируемый союз:

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

Я хотел бы создать список значений DU с помощью FsCheck, но я не хочу, чтобы ни одно из значений не относилось к случаю 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

Выполнение этого, похоже, приводит к переполнению стека, поэтому я предполагаю, что за сценой происходит то, что 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

Это работает, но имеет следующие недостатки:

  • Кажется, много работы
  • Он не использует уже доступную функцию isQux, поэтому кажется, что он слегка нарушает DRY.
  • На самом деле он не фильтрует, а только создает нужные случаи (поэтому фильтрует только путем пропуска).
  • Это не особенно удобно в сопровождении, потому что, если я когда-нибудь добавлю пятый случай к DU, мне придется запомнить, чтобы также добавить Gen для этого случая.

Есть ли более элегантный способ указать FsCheck отфильтровать значения Qux?


person Mark Seemann    schedule 26.06.2015    source источник
comment
+1 за напоминание о Базе и знакомство с Qux. Хотя это хороший вопрос и без них.   -  person ShawnMartin    schedule 08.10.2018


Ответы (2)


Вместо 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
person Kurt Schelfthout    schedule 27.06.2015

Ниже должно работать:

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 не может сгенерировать список с данным генератором.

person theimowski    schedule 26.06.2015
comment
Что ж, это позволяет обойти проблему, создав Arbitrary<DU list> вместо Arbitrary<DU>, что может решить мою насущную проблему, но, похоже, не решает основную проблему. - person Mark Seemann; 26.06.2015
comment
Разве это не создает проблему, что мы не получаем сокращающуюся поддержку DU? - person Henrik; 12.01.2017