In GenericPretty, there is an Out class with a default implementation by using GHC.Generic magic.
As you can see that I defined Person data type, and if I want to implement Out class I have to write 3 times manually since Person used Address and Names data types which should be also the instances of Out class.
I want to generate the instance declaration automatically with Template Haskell. The procedure seems simple.
1, Generate instance A for Person and seek the types which are used to define Person.
2, If the type used to define Person is not an instance A, generate it recursively.
However, gen function will not work. The code generation will not stop, I am not sure why. it could be the problem with mapM if you comment it out, the last line in gen will work.
{-# LANGUAGE CPP, TemplateHaskell,StandaloneDeriving, DeriveGeneric, DeriveDataTypeable #-}
module DerivingTopDown where
import Language.Haskell.TH
import GHC.Generics
import Data.Data
import Data.Proxy
import Control.Monad
import Text.PrettyPrint.GenericPretty
import Data.List
import Debug.Trace
import Control.Monad.State
import Control.Monad.Trans
data Person = Person Names Address
| Student Names Address
deriving (Show, Generic, Eq, Ord , Data,Typeable)
data Names = Names String
deriving (Show, Generic, Eq, Ord, Data, Typeable)
data Address = Address String
deriving (Show, Generic, Eq, Ord, Typeable, Data)
{-
data T a b = C1 a | C2 b
instance (Out a , Out b) => Out (T a b)
([],[NormalC Main.Person [(NotStrict,ConT Main.Names),(NotStrict,ConT Main.Address)],
NormalC Main.Student [(NotStrict,ConT Main.Names),(NotStrict,ConT Main.Address)]])
-}
-- instance Out Address
-- instance Out Names
-- instance Out Person
--- class name -> type name, use a stateT to store a dictionary
gen :: Name -> Name -> StateT [Name] Q [Dec]
gen cla typ = do
(tys, cons) <- lift (getTyVarCons typ)
let typeNames = map tvbName tys
let instanceType = foldl' appT (conT typ) $ map varT typeNames
let context = applyContext cla typeNames
let decltyps = (conT cla `appT` instanceType)
isIns <- lift (typ `isInstanceOf` cla)
table <- get
if isIns || elem typ table -- if it is already the instnace or we have generated it return []
then return []
else do
dec <- lift $ fmap (:[]) $ instanceD context decltyps []
modify (typ:) -- add the generated type to dictionary
let names = concatMap getSubType cons
xs <- mapM (\n -> gen cla n) names
return $ concat xs ++ dec
--return dec -- works fine if do not generate recursively by using mapM
f = (fmap fst ((runStateT $ gen ''Out ''Person) []))
getSubType :: Con -> [Name]
getSubType (NormalC n sts) = map type1 (map snd sts)
type1 :: Type -> Name
type1 (ConT n) = n
tvbName :: TyVarBndr -> Name
tvbName (PlainTV name ) = name
tvbName (KindedTV name _) = name
applyContext :: Name -> [Name] -> Q [Pred]
applyContext con typeNames = return (map apply typeNames)
where apply t = ClassP con [VarT t]
isInstanceOf :: Name -> Name -> Q Bool
isInstanceOf ty inst = do
t1 <- conT (ty)
isInstance inst [t1]
getTyVarCons :: Name -> Q ([TyVarBndr], [Con])
getTyVarCons name = do
info <- reify name
case info of
TyConI dec ->
case dec of
DataD _ _ tvbs cons _ -> return (tvbs,cons)
NewtypeD _ _ tvbs con _ -> return (tvbs,[con])
-- pp = $(stringE . show =<< getCons ''Person)
pp1 name = stringE.show =<< name
isi name = do
t1 <- [t| $name |]
isInstance ''Out [t1]
You have some incomplete function definitions (e.g.
type1
,tvbName
,getTyVarCons
) and I am running into that.I inserted a trace statement in
DerivingTopDown.hs
at the entry togen
:and then loaded this file into
ghci
:and got the following output:
So it recursed down to
GHC.Base.String
and then failed ingetTyVarCons
because thedec
for this type is:which isn't handled by the inner case statement in
getTyVarCons
.