QuickCheck Tests for Custom Type

666 views Asked by At

With the following Algebraic Data Type:

data Cons a = Cons a (Cons a) | Empty deriving (Show, Eq)

I wrote an instance of Arbitrary:

My understanding is that it's necessary to create an instance of Arbitrary for QuickCheck to know how to find a Cons Int to plug in for its tests.

instance Arbitrary (Cons Int) where
    arbitrary = elements [Empty, Cons 10 (Cons 100 (Cons 55 Empty)), Cons 1 Empty]

and a function:

makeArbitraryCons :: Gen (Cons Int)
makeArbitraryCons = arbitrary 

Then, I wrote a QuickCheck test:

prop_id_functor_law_int ::  Cons Int -> Bool
prop_id_functor_law_int x = fmap id x == id x

And it works:

ghci> quickCheck prop_id_functor_law_int
*** Failed! Falsifiable (after 1 test):
Cons 10 (Cons 100 (Cons 55 Empty))

However, when I try to run the following test, it fails:

second_functor_law_int :: (Int -> Int) -> (Int -> Int) -> Cons Int -> Bool
second_functor_law_int f g x = left == right
  where left = fmap (f . g) $ x
        right = (fmap f) . (fmap g) $ x

with this error:

ghci> quickCheck second_functor_law_int

<interactive>:418:1:
    No instance for (Show (Int -> Int))
      arising from a use of `quickCheck'
    In the expression: quickCheck second_functor_law_int
    In an equation for `it': it = quickCheck second_functor_law_int

My questions are:

  1. is it necessary to define an Arbritrary instance for a new data type (such as my Cons a)?
  2. must I create a function that creates a Gen (Cons Int) - makeArbitraryCons?
  3. lastly, what's going on when I run my second test via quickCheck second_functor_law_int?
1

There are 1 answers

2
ErikR On

1 & 2. You can either: define an Arbitrary instance or create a Gen ... function and use something like the forAll combinator.

See this answer for an example of using forAll with a Gen ... function.

  1. The signature of second_functor_law_int is:

    second_functor_law :: (Int -> Int) -> (Int -> Int) -> Cons Int -> Bool

so by running quickCheck second_functor_law_int you are asking QuickCheck to create a random Int -> Int function. Quickcheck requires its randomly generated arguments to be Show-able, and so you are getting this error since functions (in general) do not have Show instances.

With what you have it is possible to call quickCheck with specific functions f and g, e.g.:

quickCheck $ second_functor_law_int_int (+1) (*2)

Then QuickCheck will only generate random Cons Int values.

Btw, a better way to define arbitrary for an ADT like Cons is to use sized. Some examples: