Reputation: 373
Let's say I have the type StrInt defined as below
type StrInt = (String, Int)
toStrInt:: Str -> Int -> StrInt
toStrInt str int = (str, int)
I want the Show function to work as below:
Input: show (toStrInt "Hello", 123)
Output: "Hello123"
I have tried to define show as below:
instance Show StrInt where
show (str, int) = (show str) ++ (show int)
But that gives me error:
Illegal instance declaration for ‘Show StrInt’
(All instance types must be of the form (T t1 ... tn)
where T is not a synonym.
Use TypeSynonymInstances if you want to disable this.)
In the instance declaration for ‘Show StrInt’
Any ideas on how to solve this issue?
Appreciate your help!
Upvotes: 1
Views: 932
Reputation: 120751
What you're trying to do is 1. not a good idea to start with, 2. conflicts with the already-existing Show
instance and is therefore not possible without OverlappingInstances
hackery (which is almost never a good idea), and 3. the error message you're getting is not related to these problems; other class-instances with the same message may be perfectly fine but of course require the extension that GHC asks about.
The Show
class is not for generating arbitrary string output in whatever format you feel looks nice right now. That's the purpose of pretty-printing. Show
instead is supposed to yield syntactically valid Haskell, like the standard instance does:
Prelude> putStrLn $ show (("Hello,"++" World!", 7+3) :: (String,Int))
("Hello, World!",10)
Prelude> ("Hello, World!",10) -- pasted back the previous output
("Hello, World!",10)
If you write any Show
instance yourself, it should also have this property.
Again because (String, Int)
already has a Show
instance, albeit just one arising from more generic instances namely
instance (Show a, Show b) => Show (a,b)
instance Show a => Show [a]
instance Show Int
declaring a new instance for the same type results in a conflict. Technically speaking this could be circumvented by using an {-# OVERLAPPING #-}
pragma, but I would strongly advise against this because doing that kind of thing can lead to very confusing behaviour down the line when instance resolution inexplicably changes based on how the types are presented.
Instead, when you really have a good reason to give two different instances to a type containing given data, the right thing to do is generally to make it a separate type (so it's clear that there will be different behaviour) which just happens to have the same components.
data StrInt' = StrInt String Int
instance Show StrInt' where
...
That actually compiles without any further issues or need for extensions. (Alternatively you can also use newtype StrInt = StrInt (String, Int)
, but that doesn't really buy you anything and just means you can't bring in record labels.)
Instances of the form instance ClassName TypeSynonym
are possible too, and can sometimes make sense, but as GHC already informed you they require the TypeSynonymInstances
extension or one that supersedes it. In fact TypeSynonymInstances
is not enough if the synonym points to a composite type like a tuple, in that case you need FlexibleInstances
(which includes TypeSynonymInstances
), an extension I enable all of the time.
{-# LANGUAGE FlexibleInstances #-}
class C
type StrInt = (String, Int)
instance C StrInt
Upvotes: 5