Hedgehog in its tutorial provides some marketing that illustrates its "integrated shrinking", claiming that FsCheck cannot do this. There are in fact, a number of ways to rewrite their example so it also shrinks in FsCheck.
The original example hedgehog gives is:
let version =
Arb.generate<byte>
|> Gen.map int
|> Gen.three
|> Gen.map (fun (ma, mi, bu) -> Version (ma, mi, bu))
|> Gen.listOf
|> Arb.fromGen
version
|> Prop.forAll <| fun xs -> xs |> List.rev = xs
|> Check.Quick
Now obviously this does not shrink in FsCheck - because of the Arb.fromGen
the shrinker is basically explicitly disabled...
Here's a shorter way to write the Arbitrary which does shrink:
let version =
Arb.from<list<byte * byte * byte>>
|> Arb.convert (List.map (fun (ma,mi,bu) -> Version (int ma,int mi,int bu)))
(List.map (fun v -> (byte v.Major,byte v.Minor, byte v.Build)))
version
|> Prop.forAll <| fun xs -> xs |> List.rev = xs
|> Check.Quick
Falsifiable, after 2 tests (14 shrinks) (StdGen (1663895886,296389250)):
Original:
[93.69.212; 80.126.246; 69.180.214]
Shrunk:
[0.0.1; 0.0.0]
val it : unit = ()
Here's another way to do it:
Arb.from<list<byte * byte * byte>>
|> Prop.forAll <| fun xs ->
let vs = List.map (fun (ma,mi,bu) -> Version(int ma,int mi,int bu)) xs
vs |> List.rev = vs
|> Prop.label (vs.ToString())
|> Check.Quick
which gives:
Falsifiable, after 2 tests (13 shrinks) (StdGen (1974807201,296389254)):
Label of failing property: [0.0.1; 0.0.0]
Original:
[(89uy, 207uy, 241uy); (8uy, 42uy, 160uy)]
Shrunk:
[(0uy, 0uy, 1uy); (0uy, 0uy, 0uy)]