Search code examples
haskelltypesderivingdata-kinds

How can I automatically derive Typeable instance for DataKinds derived types?


I had some types like this:

data Currency = USD | EUR
              deriving (Show, Typeable)

data Money :: Currency -> * where
  Money :: Int -> Money c
  deriving (Show, Typeable)

And I wanted to use typeOf with them in this function:

findRate :: Money a -> Rates -> Maybe Double
findRate a = M.lookup (typeOf a)

That didn't work, because the type a in findRate didn't have a Typeable instance. So I fixed it by doing this:

deriving instance Typeable USD
deriving instance Typeable EUR
findRate :: (Typeable a) => Money a -> Rates -> Maybe Double

However, that becomes a lot of boilerplate when the number of currencies increase. Is there a way to specify that all types of kind Currency should derive a Typeable instance?

EDIT: Also, a way to make it infer that in Money a the a is Typeable would be nice, so then I don't need to add (Typeable a) => everywhere. That's minor though.


Solution

  • Yes, you can use the AutoDeriveTypeable extension.

    For the other part, the closest thing I could think of was to put Typeable c => inside the GADT definition as follows:

    {-# LANGUAGE AutoDeriveTypeable #-}
    {-# LANGUAGE GADTs #-}
    {-# LANGUAGE DataKinds #-}
    
    import Data.Typeable
    import qualified Data.Map as M
    
    type Rates = M.Map TypeRep Double
    
    data Currency = USD | EUR
                  deriving (Show, Typeable)
    
    data Money :: Currency -> * where
      Money :: Typeable c => Int -> Money c
    
    instance Show (Money c) where
        show (Money n) = "Money " ++ show n
    
    findRate :: Money a -> Rates -> Maybe Double
    findRate a@(Money _) = M.lookup (typeOf a)
    

    Note though:

    • By the nature of GADTs, this requires actually evaluating a to get the Typeable context out of it, which typeOf itself doesn't.
    • This seems to ruin the ability to derive Show automatically for the GADT.