I have a class identical to Show and I would like to make an instance of this class for each tuple type. Usually this is done by writing separately instances for each tuple type
instance (Show a, Show b) => Show (a,b) where
showsPrec _ (a,b) s = show_tuple [shows a, shows b] s
instance (Show a, Show b, Show c) => Show (a, b, c) where
showsPrec _ (a,b,c) s = show_tuple [shows a, shows b, shows c] s
instance (Show a, Show b, Show c, Show d) => Show (a, b, c, d) where
showsPrec _ (a,b,c,d) s = show_tuple [shows a, shows b, shows c, shows d] s
...
Writing one instance per tuple type results in a lot of boilerplate and it's easy to see the common pattern shared among all the showPrec implementations. To avoid this kind of boilerplate I thought I could use Data.Generics from Scrap your boilerplate and implement a folding over tuples, like
showTuple = intercalate " " . gmapQ ("" `mkQ` show)
But showTuple doesn't work for some reason
> showTuple (1,2)
" "
I think the problem is the fact that show is polymorphic because if I specialize showTuple then it works
showTupleInt = intercalate " " . gmapQ ("" `mkQ` (show :: Int -> String))
> showTupleInt (1::Int,2::Int)
"1 2"
I have checked the code of gshow that does something similar to what I need, but I can't figure out how it works. If I try to import its code into GHCI I get an error:
> let gshows = (\t -> showChar '('
. (showString . showConstr . toConstr $ t)
. (foldr (.) id . gmapQ ((showChar ' ' .) . gshows) $ t)
. showChar ')'
) `extQ` (shows :: String -> ShowS)
<interactive>:262:59:
Could not deduce (a ~ d)
from the context (Data a)
bound by the inferred type of
gshows :: Data a => a -> String -> String
at <interactive>:(259,5)-(264,44)
or from (Data d)
bound by a type expected by the context:
Data d => d -> String -> String
at <interactive>:262:33-65
`a' is a rigid type variable bound by
the inferred type of gshows :: Data a => a -> String -> String
at <interactive>:259:5
`d' is a rigid type variable bound by
a type expected by the context: Data d => d -> String -> String
at <interactive>:262:33
Expected type: d -> String -> String
Actual type: a -> String -> String
In the second argument of `(.)', namely `gshows'
In the first argument of `gmapQ', namely
`((showChar ' ' .) . gshows)'
In the second argument of `(.)', namely
`gmapQ ((showChar ' ' .) . gshows)'
So I have two questions:
- what's wrong with
showTupleand how can I fix it such that it will work with tuples of any size - how
gshowworks and why if I import its code on GHCI I get that error?
EDIT: I'm studying Data.Generics and in general SYM, so I would like to use that module. I will accept an answer only if it uses just that module. Thanks.
You're right that
showTupledoesn't work because of the polymorphism ofshow. The problem is thatmkQwants to pick out one specific type:The
bin the type signature has to be one specific type for each use ofmkQ- without the type signature the defaulting rules are probably picking something (not sure what!), whereas with the type signature it picksInt.Your
showTupleIntdoes work on tuples of any size, but of course not on tuples of any type.The problem with defining
gshowsin GHCi is that it really needs a type signature to be able to type check, because of the recursive use ofgshowsin its own definition at a different type to the original invocation. Without the type signature the type-checker wants the definition ofgshowsto have exactly the same instantiation of the type variable as the use ofgshows- which shows up as theCould not deduce (a ~ d)type error.You can see this by putting it in a source file with and without the type signature - with the signature it typechecks fine, without it you get a similar error to the one you got, if you first use
:set -XNoMonomorphismRestriction.gshowsworks at all because of the type ofgmapQ:In contrast to
mkQ, the parameter it takes is itself polymorphic - note the nestedforall.Although your
showTuplealso usesgmapQ, it's too late -mkQhas already caused the trouble by forcingshowto only work on one type.You also can't use
showdirectly withgmapQdirectly because the constraints are different -gmapQwants something that will work on any instance ofData, whereasshowis constrained byShow.gshowsnever actually uses theShowtype class generically, although it does useshowsspecialised toString.It's difficult to prove a negative in a case like this, but I'm fairly sure that you can't write anything like
showTuplethat will use theShowclass polymorphically using justsyb, because it simply doesn't have anything that can "recognise" types that have a particular instance. That's whysyb-with-classexists.Also, if you really do want something that just works on a single level of the type structure, i.e. shows any size tuple but uses something else for the elements of the tuple, then
sybis arguably the wrong solution because it is designed for operating recursively and finding things at any level of a data structure. My view is that theGHC.Genericssolution is the nicest one for implementingshowTuple.