logo

Write you some QuickCheck - Generating random integers

This post is part of a series of posts on implementing a minimal version of QuickCheck from scratch. The source code is available on GitHub.

In the previous post I’ve generated random booleans. In this post, I’ll be generating random 32-bit signed integers.

Recall that the Gen<'a> type is ported already in this previous post as:

/// <summary>
/// A generator for values of type 'a.
/// </summary>
type Gen<'a> =
    private
    | Gen of (int -> StdGen -> 'a)

This means we can write functions that handle state (—monadster!) combining:

So, to generate numbers, we need:

Porting Gen’s sized

/// <summary>
/// Used to construct generators that depend on the size parameter.
/// </summary>
/// <param name="g">A generator for values of type 'a.</param>
let sized g =
    Gen(fun n r ->
        let (Gen m) = g n
        m n r)

Given the above, a 32-bit signed integer generator can be written as:

let int = Gen.sized (fun n -> Gen.choose (-n, n))

val int : Gen<int>

Here are some sample integers:

> Gen.int |> Gen.generate;;
val it : int = 0

> Gen.int |> Gen.generate;;
val it : int = -18

> Gen.int |> Gen.generate;;
val it : int = 0

> Gen.int |> Gen.generate;;
val it : int = 6

> Gen.int |> Gen.generate;;
val it : int = -6

> Gen.int |> Gen.generate;;
val it : int = 1

> Gen.int |> Gen.generate;;
val it : int = -2

> Gen.int |> Gen.generate;;
val it : int = 5

> Gen.int |> Gen.generate;;
val it : int = -4

> Gen.int |> Gen.generate;;
val it : int = -18

> Gen.int |> Gen.generate;;
val it : int = 5

> Gen.int |> Gen.generate;;
val it : int = -17

> Gen.int |> Gen.generate;;
val it : int = -7

> Gen.int |> Gen.generate;;
val it : int = -3

> Gen.int |> Gen.generate;;
val it : int = 1

> Gen.int |> Gen.generate;;
val it : int = 12

> Gen.int |> Gen.generate;;
val it : int = 5

> Gen.int |> Gen.generate;;
val it : int = 6

> Gen.int |> Gen.generate;;
val it : int = -8

> Gen.int |> Gen.generate;;
val it : int = -10

But there’s a pattern here (can you tell?); all the generated integers are in the range [-30, 30] — that’s because generate sets the size for the generators to be up to 301.

Generating numbers in range [-999, 999]

I can run the integer generator again, but this time I’ll override the runtime size.

This prompts me to port Gen’s resize

/// <summary>
/// Overrides the size parameter. Returns a generator which uses the given size
/// instead of the runtime-size parameter.
/// </summary>
/// <param name="n">The size that's going to override the runtime-size.</param>
let resize n (Gen m) = Gen(fun _ r -> m n r)

Finally, here are some sample integers in the range [-999, 999]:

> Gen.int |> Gen.resize 999 |> Gen.generate;;
val it : int = -808

> Gen.int |> Gen.resize 999 |> Gen.generate;;
val it : int = 797

> Gen.int |> Gen.resize 999 |> Gen.generate;;
val it : int = -676

> Gen.int |> Gen.resize 999 |> Gen.generate;;
val it : int = 3

> Gen.int |> Gen.resize 999 |> Gen.generate;;
val it : int = 304

> Gen.int |> Gen.resize 999 |> Gen.generate;;
val it : int = -476

> Gen.int |> Gen.resize 999 |> Gen.generate;;
val it : int = 276


  1. That’s how the generate function works in QuickCheck 2; It’s slightly different (and easier to use) compared to QuickCheck 1.