Yige Song
Yige Song

Reputation: 373

Haskell: Define Show function for a user defined type, which is defined by "type" key word

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

Answers (1)

leftaroundabout
leftaroundabout

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.

  1. 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.

  2. 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.)

  3. 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

Related Questions