12
votes

I have a type class Atomic, which defines functions for converting certain types to/from a wrapper value (Atom). I'd like to define a QuickCheck property which states: "for all instances of Atomic, any value may be stored and retrieved safely". The property looks like this:

class Atomic a where
    toAtom :: a -> Atom
    fromAtom :: Atom -> Maybe a

prop_AtomIdentity x = fromAtom (toAtom x) == Just x

However, if I just try to run that property through QuickCheck, it just picks one instance (Bool) and tests it. I'm currently working around that by defining type signatures for each supported atomic type in the test list, but this is verbose and error-prone:

containerTests =
    [ run (prop_AtomIdentity :: Bool -> Bool)
    , run (prop_AtomIdentity :: Word8 -> Bool)
    , run (prop_AtomIdentity :: String -> Bool)
    {- etc -} ]

I'm trying to define a function which will do this automatically:

forallAtoms :: (Atomic a, Show a) => (a -> Bool) -> [TestOptions -> IO TestResult]
forallAtoms x =
    [ run (x :: Bool -> Bool)
    , run (x :: Word8 -> Bool)
    , run (x :: String -> Bool)
    {- etc -} ]

containerTests = forallAtoms prop_AtomIdentity

But it fails with a typecheck error:

Tests/Containers.hs:33:0:
    Couldn't match expected type `Word8' against inferred type `String'
    In the first argument of `run', namely `(x :: Word8 -> Bool)'
    In the expression: run (x :: Word8 -> Bool)
    In the expression:
        [run (x :: Bool -> Bool), run (x :: Word8 -> Bool),
         run (x :: String -> Bool)]

Is there a better way to test a QC property against multiple types? If not, can forallAtoms be made to work or is that not supported by the type system?

1

1 Answers

12
votes

I cannot compile your code, so ... blind shot:

try

forallAtoms :: (forall a. (Atomic a, Show a) => a -> Bool) -> [TestOptions -> IO TestResult]

as a type signature. This needs the -XRankNTypes language extension.

The problem you have, as I see it, is that GHC tries to find one type to insert for a in x :: (a -> Bool) for the entire function scope, but you already give three different there.