Reputation: 77
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?
Upvotes: 4
Views: 423
Reputation: 5992
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.
Upvotes: 3