Search code examples
haskelltypeclassquickcheck

Testing type classes with Quickcheck, variable number of parameters


I have a ring type class which looks like this:

class Ring a where
  addId  :: a
  addInverse :: a -> a
  mulId  :: a
  add :: a -> a -> a
  mul :: a -> a -> a

For this class I have several instances, e.g.

instance Ring Matrix where ...
instance Ring Integer where ...
instance Ring Modulo where ...

In order to test these instances, I have following quickcheck tests:

prop_AddId :: (Ring a, Eq a, Arbitrary a) => a -> Bool
prop_AddInv :: (Ring a, Eq a, Arbitrary a) => a -> Bool
prop_MulId :: (Ring a, Eq a, Arbitrary a) => a -> Bool
prop_AddCommutative :: (Ring a, Eq a, Arbitrary a) => a -> a -> Bool
prop_AddAssociative :: (Ring a, Eq a, Arbitrary a) => a -> a -> a -> Bool
prop_MulAssociative :: (Ring a, Eq a, Arbitrary a) => a -> a -> a -> Bool
prop_Distributive :: (Ring a, Eq a, Arbitrary a) => a -> a -> a -> Bool

I'm unsure how to run these testcases for all my class instances. I found a solution here which leads to the following:

forallRings :: (forall a. (Ring a, Arbitrary a, Eq a) => a -> Bool) -> [IO ()]
forallRings x =
    [ quickCheck (x :: Matrix -> Bool)
    , quickCheck (x :: Integer -> Bool)
    , quickCheck (x :: Modulo -> Bool)
    ]
forallRings2 :: (forall a. (Ring a, Arbitrary a, Eq a) => a -> a -> Bool) -> [IO ()]
forallRings2 x =
    [ quickCheck (x :: Matrix -> Matrix -> Bool)
    , quickCheck (x :: Integer -> Integer -> Bool)
    , quickCheck (x :: Modulo -> Modulo -> Bool)
    ]
forallRings3 :: (forall a. (Ring a, Arbitrary a, Eq a) => a -> a -> a -> Bool) -> [IO ()]
forallRings3 x =
    [ quickCheck (x :: Matrix -> Matrix -> Matrix -> Bool)
    , quickCheck (x :: Integer -> Integer -> Integer -> Bool)
    , quickCheck (x :: Modulo -> Modulo -> Modulo -> Bool)
    ]

ringTests :: IO ()
ringTests = sequence_ $
            forallRings propAddId
            ++ forallRings prop_AddInv
            ++ forallRings prop_MulId
            ++ forallRings2 prop_AddCommutative
            ++ forallRings3 prop_AddAssociative
            ++ forallRings3 prop_MulAssociative
            ++ forallRings3 prop_Distributive

I am somewhat unsatisfied with this solution. I find the forAllRingsX functions a little ugly and repetetive. The reason is that my tests have a different number of parameters. Is there a better way (i.e. one with less boiler plate code) to test all instances?


Solution

  • I think a typeclass function is the way to go here. One simple way is to add another member to the class Ring like ringTests :: (Eq a, Arbitrary a) => proxy a -> IO Result. The proxy argument will be necessary so that caller can specify what type to test: ringTests (Proxy :: Proxy Matrix).

    ringTests can have a default implementation that should be easy to write; you may need ScopedTypeVariables to get the type variable in scope.

    You can also of course put the test function in a separate class, again with a default implementation. Then you'd just have to write instance RingTests Matrix where and so forth, with no implementation necessary.