QuickCheck has a function called sample
which, given a generator, prints some examples values to the standard output (stdout).
It’s signature is:
sample :: Show a => Gen a -> IO ()
Using the choose
generator we can use sample
to generate random integers in the inclusive range [0, 9] and print them:
λ sample $ choose (0, 9 :: Int)
Output:
1
5
0
2
3
4
6
5
5
4
1
FsCheck also defines a sample
function, though it behaves in a slightly different way, as it takes two additional arguments size
and n
:
val sample : size:int -> n:int -> gn:Gen<'a> -> 'a list
// `size` is the size of generated test data
// `n` is the number of samples (defaults to 11 in QuickCheck)
Let’s pause here and run sample
by supplying different values for size
.
open FsCheck.Gen
let sample' = sample 0 11 // Size = 0, Number of samples = 11
let sample'' = sample' <| choose (0, 9)
Output:
Length = 11
[0]: 4
[1]: 0
[2]: 4
[3]: 0
[4]: 3
[5]: 9
[6]: 3
[7]: 9
[8]: 2
[9]: 8
[10]: 2
However, since size is 0
the result should be:
Length = 11
[0]: 0
[1]: 0
[2]: 0
[3]: 0
[4]: 0
[5]: 0
[6]: 0
[7]: 0
[8]: 0
[9]: 0
[10]: 0
open FsCheck.Gen
let sample' = sample 1000 11 // Size = 1000, Number of samples = 11
let sample'' = sample' <| choose (0, 9)
Output:
Length = 11
[0]: 8
[1]: 6
[2]: 2
[3]: 3
[4]: 7
[5]: 3
[6]: 1
[7]: 2
[8]: 6
[9]: 2
[10]: 0
The above result looks incorrect, in fact it looks the same as the first result, where size was 0.
Since size is 1000
the result should look like:
Length = 11
[0]: -78
[1]: 347
[2]: 242
[3]: 707
[4]: 414
[5]: 49
[6]: 551
[7]: 45
[8]: 897
[9]: -99
[10]: 77
At this point, we observed that if a generator ignores size (e.g. as choose
does) then sample
ignores it’s size
parameter.
Instead of the choose
generator, the following example uses generate<int>
.
open FsCheck.Arb
open FsCheck.Gen
let sample' = sample 0 11 // Size = 0, Number of samples = 11
let sample'' = sample' <| generate<int>
The results are now looking good:
Length = 11
[0]: 0
[1]: 0
[2]: 0
[3]: 0
[4]: 0
[5]: 0
[6]: 0
[7]: 0
[8]: 0
[9]: 0
[10]: 0
That’s because generate<int>
takes size
into account.
open FsCheck.Arb
open FsCheck.Gen
let sample' = sample 1000 11 // Size = 1000, Number of samples = 11
let sample'' = sample' <| generate<int>
The results are now looking good:
Length = 11
[0]: -98
[1]: 307
[2]: 142
[3]: 507
[4]: 414
[5]: 39
[6]: 501
[7]: 4
[8]: 807
[9]: -81
[10]: 31
That seems to be the purpose of the sized
function.
For the sake of completeness, here’s the original example with choose
rewritten to use sized
:
open FsCheck.Gen
let sample' = sample 0 11 // Size = 0, Number of samples = 11
let sample'' = sample' <| (sized <| fun s -> choose (0, s))
Results to the following, which is now correct:
Length = 11
[0]: 0
[1]: 0
[2]: 0
[3]: 0
[4]: 0
[5]: 0
[6]: 0
[7]: 0
[8]: 0
[9]: 0
[10]: 0
open FsCheck.Gen
let sample' = sample 1000 11 // Size = 1000, Number of samples = 11
let sample'' = sample' <| (sized <| fun s -> choose (-s, s))
Results to the following, which now looks correct:
Length = 11
[0]: 105
[1]: 682
[2]: -239
[3]: 658
[4]: -6
[5]: 9
[6]: 203
[7]: -43
[8]: 304
[9]: -125
[10]: -423
The above F# examples use the backward pipe <|
operator to look similar to the Haskell examples. In F#, it’s idiomatic to use the forward pipe operator instead.
Using the forward pipe operator, the previous example would be:
let result =
(fun s -> Gen.choose (-s, s))
|> Gen.sized
|> Gen.sample 1000 11