Consider the following type (in Haskell):
data CType = CInt | CDouble | CProduct String [(String, CType)]
I would like to have a function that would generate corresponding Haskell type declarations. Constructors CInt
and CDouble
should correspond to Int
and Double
types thus result in no declaration. (CProduct t ts)
should correspond to the record type with name and constructor defined by t
and fields defined by ts
list. For example:
ty :: CType
ty = CProduct "Type1" [("proj10", CDouble), ("proj11", CProduct "Type0" [("proj0", CInt)])]
should result in two definitions:
data Type0 = Type0 {proj0 :: Int}
data Type1 = Type1 {proj10 :: Double, proj11 :: Type0}
To define this function I use Template Haskell:
genType :: CType -> Q Type
genType CInt = return (ConT ''Int)
genType CDouble = return (ConT ''Double)
genType (CProduct s t) =
do
l <- lookupTypeName s
case l of
Just sn -> return (ConT sn)
Nothing ->
do
sn <- newName s
return (ConT sn)
genField :: (String, CType) -> Q VarStrictType
genField (s , t) =
do
sn <- newName s
tn <- genType t
return (sn, NotStrict, tn)
genDecl :: CType -> Q [Dec]
genDecl CInt = return []
genDecl CDouble = return []
genDecl (CProduct s t) =
do
ts <- fmap join . mapM (genDecl . snd) $ t
res <- lookupTypeName s
case res of
Just _ -> return ts
Nothing ->
do
sn <- newName s
sc <- newName s
fn <- mapM genField $ t
let
dt = DataD [] sn [] [RecC sc fn] []
return (dt : ts)
When I invoke the function with $(genDecl ty)
for ty :: CType
defined above I get following error:
The exact Name ‘Type0_adN6’ is not in scope …
Probable cause: you used a unique Template Haskell name (NameU),
perhaps via newName, but did not bind it
But when I generate definitions one by one everything is fine:
$(fmap (\[x, y] -> [y]) . genDecl $ ty)
$(genDecl $ ty)
So the question is: how to properly add type declarations in Q
monad to generate them all at once?