A more explicit approach is to create a custom derivation via TemplateHaskell. The following code describes the logic for generating custom Show
instances for a given datatype:
genShow :: Name -> Q [Dec]
genShow typName =
do -- Getting type definition
(TyConI d) <- reify typName -- Get all the information on the type
-- Extracting interesting info: type name, args and constructors
let unpackConstr c = case c of
NormalC cname args -> (cname, length args)
InfixC _ cname _ -> (cname, 2)
RecC cname args -> (cname, length args)
ForallC _ _ c -> unpackConstr c
_ -> error "you need to figure out GADTs yourself"
(type_name, targs, constructors) <-
case d of
d@(DataD _ name targs _ cs _) ->
return (name, targs, map unpackConstr cs)
d@(NewtypeD _ name targs _ con _) ->
return (name, targs, [unpackConstr con])
_ -> error ("derive: not a data type declaration: " ++ show d)
-- Extracting name from type args
let targName targ = case targ of
PlainTV tvname _ -> tvname
KindedTV tvname _ _ -> tvname
-- Manually building AST for an instance.
-- Essentially, we match on every constructor and make our `show`
-- return it as a string result.
i_dec <- instanceD (cxt [])
(appT (conT (mkName "Show")) (foldl appT (conT type_name)
(map (varT . targName) targs)))
[funD (mkName "show") (flip map constructors $ \constr ->
let myArgs = [conP (fst constr) $ map (const wildP) [1..snd constr]]
myBody = normalB $ stringE $ nameBase $ fst constr
in clause myArgs myBody []
)]
return [i_dec]
Then, you simply do
data MyData = D Int | X
$(genShow ''MyData)
...and you can happily show
it. Note that both code snippets must be placed in separate modules and you need to use TemplateHaskell
extension.
I took a lot of inspiration from this article.