Gen and MonadGen

How QuickCheck Generates Random Data

So, how does QuickCheck generate random data? Essentially, it uses a linear congruential generator to produce random numbers. Values can be created based on that number. Let's give some examples.

Let's say the randomly generated number is x where x is a Number:

  • Primitives:
    • Boolean: if 0 < x and x < 0.5, we might produce a true value and false otherwise.
    • Int: we can convert x to an Int via ceil
    • Char: we can map x to an Int and then produce a corresponding Unicode value via toCharCode.
    • String: by generating numerous Char values, we can combine them together into a String value.
  • Containers:
    • Maybe: given a Gen a, we can generate Nothing if 0 < x && x < 0.5 and Just a otherwise.
    • Either: given a Gen a and a Gen b, we can generate Left a if 0 < x && x < 0.5 and Right b otherwise.
    • List: given a Gen a and an Int indicating the number of values to generate, we can use the first generator to produce an a value and cons that onto Nil or the rest of the list.
    • Array: we can generate a List a and then use fromUnfoldable

The Gen Monad and its MonadGen Type Class

In purescript-gen, MonadGen is the type class that has a default implementation via Gen. To see all of the definitions of the types used in Gen, look below:

-- This Int will always be between 1 and 2147483647,
-- which is (2^31 - 1), a Mersenne prime.
-- It is used in a linear congruential generator.
newtype Seed = Seed Int -- This will always be a positive integer
type Size = Int
type GenState = { newSeed :: Seed, size :: Size }

newtype Gen a = State GenState a

QuickCheck uses the Gen monad (i.e. generator monad) to generate random data. QuickCheck.Gen exports most of the package's functions (i.e. the "combinators" as they are called), not but all of them.

Furthermore, some data types combinators exist in other libraries. For example:

The Importance of the Seed Value

The Seed value is used to produce the random data. If you run a propety test and it fails, the failure message will also include the seed used to produce that data. Once you update your code to fix the bug, how would you know whether it fixed that particular instance? You would run the test and specify that it should use that specific seed.

As an example, this repository includes some example programs in the Projects folder. One of my property tests failed, so I saved the seeds here:

I can use those seeds to help troubleshoot why these problems occurred and ensure that the bug has indeed been fixed.