Michael Beidler
Michael Beidler

Reputation: 118

Is it possible to map quickCheck over a list of types?

Suppose we have a quickcheck property verifying that an instance of Functor satisfies the identity law:

-- | A covariant functor.
class Map m where
    (<@>) :: (a -> b) -> m a -> m b

data Proxy a = Proxy

-- | The functor identity law.
prop_mapIdentityLaw :: (Eq (m a), Show (m a), Map m)
                    => Proxy (m a)
                    -> m a
                    -> Bool
prop_mapIdentityLaw _ x = (id <@> x) == id x

I would like to write a function that runs this property against instances I create. Something roughly akin to:

verify :: IO ()
verify =
    mapM_ quickCheck [ prop_mapIdentityLaw t | t <- types ]
  where 
    types = 
      [ Proxy :: Proxy (Id Int) ]

If I were to add another Proxy to my list of types (e.g. Proxy :: Proxy (Option Int)), obviously that will not work, because Proxy has type Proxy (Id Int).

I used Haskell a lot a few years ago, but I'm unfamiliar with some of the newer extensions. I'm wondering if there is a way to achieve this with RankNTypes and DataKinds or some other combination of extensions.

Upvotes: 1

Views: 105

Answers (1)

chi
chi

Reputation: 116139

You need to pack the relevant dictionaries (witnesses of the instances) in an existential type, so to make the "type list" homogeneous:

-- Carries a Proxy with a bunch of dictionaries
data ProxyA where
  ProxyA :: (Eq (m a), Show (m a), Map m, Arbitrary (m a)) => Proxy (m a) -> ProxyA

-- | The functor identity law.
prop_mapIdentityLaw :: (Eq (m a), Show (m a), Map m)
                    => Proxy (m a)
                    -> m a
                    -> Bool
prop_mapIdentityLaw _ x = (id <@> x) == id x

verify :: IO ()
verify = forM_ types $ \ (ProxyA p) -> quickCheck $ prop_mapIdentityLaw p
  where 
    types :: [ ProxyA ]
    types =
      [ ProxyA (Proxy :: Proxy (Id Int))
      , ProxyA (Proxy :: Proxy (Id Char)) 
      ]

Note that, because of how existential types work, you can't make a list of the partially applications as in [ prop_mapIdentityLaw p | ProxyA p <- types ] since this list is not homogeneous. If you add quickCheck on top, then it becomes homogeneous.

Upvotes: 2

Related Questions