fscheck / FsCheck

Random Testing for .NET
https://fscheck.github.io/FsCheck/
BSD 3-Clause "New" or "Revised" License
1.15k stars 154 forks source link

Update docs with new `Arbitrary` API based on `ArbMap` #644

Open LAC-Tech opened 9 months ago

LAC-Tech commented 9 months ago

Reading https://fscheck.github.io/FsCheck//TestData.html It says:

"Arb.from<'a> returns the registered Arbitrary instance for the given type 'a"

But that's not available, this won't compile.

open FsCheck.FSharp
open Laterbase.Core
open System

Console.Clear ()

let genBytes = Arb.from<byte>

Nor is it in the API docs

https://fscheck.github.io/FsCheck/reference/fscheck-fsharp-arb.html

I'm basically trying to re-write the 2.0 function in 3.0, and I'm at a bit of a loss.

Arb.generate<byte> |> Gen.arrayOfLength 16
kurtschelfthout commented 9 months ago

Have a look at ArbMap which now contains the mapping type to Arb instance explicitly. On 14 Sep 2023, at 23:06, Lewis Campbell @.***> wrote: Reading https://fscheck.github.io/FsCheck//TestData.html It says: "Arb.from<'a> returns the registered Arbitrary instance for the given type 'a" But that's not available. open FsCheck.FSharp open Laterbase.Core open System

Console.Clear ()

let genBytes = Arb.from Nor is it in the API docs https://fscheck.github.io/FsCheck/reference/fscheck-fsharp-arb.html I'm basically trying to re-write the 2.0 function in 3.0, and I'm at a bit of a loss. Arb.generate |> Gen.arrayOfLength 16

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you are subscribed to this thread.Message ID: @.***>

bartelink commented 9 months ago

Per #638, I have a:

module ArbMap =
    let defGen<'t> = ArbMap.defaults |> ArbMap.generate<'t>

That I sprinkle around

The changes are listed in https://github.com/fscheck/FsCheck/blob/master/FsCheck%20Release%20Notes.md

🤔 (I found it by searching for generate) Perhaps there could be an explicit long form summary of moved methods in there so people can search for the string Arb.generate etc ? Or maybe if the changelog was formatted a la KeepAChangeLog e.g. https://github.com/jet/equinox/blob/master/CHANGELOG.md#changed, then you could look up Arb in the changes ?

LAC-Tech commented 9 months ago

What would help me - as someone new to the library in general - is a very basic example of how to use the built in generators to build your own generators.

Per #638, I have a:

module ArbMap =
    let defGen<'t> = ArbMap.defaults |> ArbMap.generate<'t>

That I sprinkle around

This is equivalent to the old Arb.generate?

bartelink commented 9 months ago

Yes - it's equivalent. See this PR for some code that uses it https://github.com/jet/equinox/pull/412/files

In general I'd recommend downloading the FsCheck solution and doing a text search within in - there's some examples and you also get to see the relationships. The changelog linked above actually does detail the transitions quite well if you read it carefully, but I've been in your shoes wondering where all the cheese has gone and when the yaks are going to part ways and let you move on, so I can definitely empathise...

bartelink commented 4 months ago

I also learned/realised/figured out recently that the best/typical way to decorate the generation is by having a "factory" Arb that takes the IArbMap as an argument, i.e.:

type ModelArbs private () =
    // Builds based on Arbs in the ArbMap
    static member CatalogEntry map: Arbitrary<CatalogEntry> =
        ArbMap.generate map
        |> Gen.filter (not << System.String.IsNullOrWhiteSpace)
        |> Gen.map CatalogEntry.fromId
        |> Arb.fromGen
    // Some UoM types (such as tokens, which is a `float` with a UoM) won't serialize if NaN / Infinity
    static member NormalFloats map: Arbitrary<float> = Arb.fromGen <| gen {
        let! NormalFloat f = ArbMap.generate map
        return f }

https://stackoverflow.com/a/77982678/11635

cc @kurtschelfthout It took me ages to work this out as I don't believe the docs mention it in the samples. For people trying to use FsCheck via FsCheck.Xunit/Nunit (and without reading the source), I suspect the way that the Factory signature for an Arb works (and that you can build an ArbMap by adding them singly, adding a factory singly, scan a type (and that both signatures work in that context)) is far from obvious as it stands.

But I do appreciate that you said no to the ArbMap.defGen thing I have above - ultimately that's a misleading local maximum; I'll be replacing it with the factory technique intead in my normal usage

kurtschelfthout commented 4 months ago

I know this is far from obvious. Besides the obvious problem that the documentation is out of date, the problem of automatic generation is actually pretty thorny. Because you often want to customize the generation of a type that's nested inside some other types, and has yet other types as parameters. So you could say I started with the idea of "just put all the types in a Type -> generator map, but then realized that doesn't quite work for common use cases like collections List<'a> and Dictionary<Tuple<'a,'b>, ('c,'d)> in that you need some sort of factory to put all the generic type args together, if that makes sense. So it ended up being a dependency injection type thing, which I otherwise hate...so yeah.