Is there something like Show (deriving Show) that only uses an algebraic datatype's constructors? (please don't mind that I'm using the word constructor, I don't know the right name...)
The reason for this question is that with many of my algebraic datatypes I don't want to bother with making their contents also derive Show, but I still want to gain some debug information about the constructor used without having to implement showing every constructor...
An alternative could be a function that gives me the constructors name, that I can use in my own implementation of show.
This of course needs to do some compiler magic (auto deriving) because the whole idea behind is to not have to explicitely implement every data constructors string representation.
CodePudding user response:
For a type with a Data.Data.Data instance, this function is easy: it's merely
showConstr . toConstr :: Data a => a -> String
For example,
Prelude Data.Data> showConstr . toConstr $ Just 5
"Just"
For a type which doesn't implement Data, it is fairly hopeless, because you can't look inside the type to see how it's implemented. But since you define these types yourself, you can merely ensure they have a Data instance. It is derived automatically with deriving Data, provided you have enabled DeriveDataTypeable.
Note that Data is only suitable for types which are algebraic and transparent through and through. You won't be able to derive an instance for a type containing, say, a function in one of its fields. So this may not be as much of a reprieve from the tyranny of Show as you'd hoped: a lot of the types Show can't support will also be rejected by Data. Generic may provide a more general solution. I'm no expert on generics, but conNameOf looks promising.
CodePudding user response:
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:
import Language.Haskell.TH
mk_constr_show :: Name -> Q [Dec]
mk_constr_show typeName =
do -- Getting type definition
(TyConI d) <- reify typeName
-- Extracting interesting info: type name and the constructors
(type_name, constructors) <-
-- Our code should work for both `data` and `newtype`
case d of
d@(DataD _ name _ _ cs _) ->
return (name, map (\(NormalC cname args) -> (cname, length args)) cs)
d@(NewtypeD _ name _ _ (NormalC cname args) _) ->
return (name, [(cname, length args)])
_ -> error ("derive: not a data type declaration: " show d)
-- 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")) (conT type_name))
[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
$(mk_constr_show ''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.
