2
votes

GHC can't derive Read or Show for complicated GADTs, so I attempted to define custom instances below that satisfy read . show == id. I've simplified the example as much as possible (while still triggering the runtime error like my real code). I decided to let GHC do the heavy lifting of writing Read and Show instances by making newtype wrappers for each GADT constructor (more accurately: for each type output by the GADT). In the Read/Show instances, I simply read/show the newtype wrapper and convert where necessary. I assumed this was foolproof: I'm letting GHC define all of the non-trivial instances. However, I seem to have done something wrong.

In my real code, Foo below is a complicated GADT that GHC won't derive Show or Read for. Since Foo is a wrapper (in part) around a newtype, I use the derived Show/Read instances for that.

{-# LANGUAGE FlexibleContexts, FlexibleInstances, GADTs, ScopedTypeVariables #-}

import Text.Read (Read(readPrec))

newtype Bar r = Bar r deriving (Show, Read)
newtype Foo r = Foo (Bar r)
-- use the GHC-derived Show/Read for Bar
instance (Show r) => Show (Foo r) where
  show (Foo x) = show x
instance (Read r) => Read (Foo r) where
  readPrec = Foo <$> readPrec

This instance seems to work: I can call read . show and I get back the input. Now I have a wrapper around Foo:

data U t rep r where
  U1  :: t r -> U t Int r
  U2 :: t r -> U t Char r
-- use the Read/Show instances for U1Wrap and U2Wrap
newtype U1Wrap t r = U1Wrap {unU1Wrap :: t r} deriving (Show, Read)
newtype U2Wrap t r = U2Wrap (t r) deriving (Show, Read)
instance (Read (t r)) => Read (U t Int r) where
  readPrec = (U1 . unU1Wrap) <$> readPrec
instance (Read (U2Wrap t r)) => Read (U t Char r) where
  readPrec = do
    x <- readPrec
    return $ case x of
      (U2Wrap y) -> U2 y
instance (Show (t r)) => Show (U t Int r) where
  show (U1 x) = show $ U1Wrap x
instance (Show (t r)) => Show (U t Char r) where
  show (U2 x) = show (U2Wrap x :: U2Wrap t r)

Like Foo, U is a complicated GADT, so I define custom newtype wrappers for each output type of the GADT. Unfortunately, my Show/Read instances don't work:

main :: IO ()
main = do
  let x = U1 $ Foo $ Bar 3
      y = U2 $ Foo $ Bar 3
  print $ show (read (show x) `asTypeOf` x)
  print $ show (read (show y) `asTypeOf` y)

main prints the first line, but I get Exception: Prelude.read: no parse on the second line.

This is my first time using Read, so I suspect I'm doing something incorrectly, though I don't see what that is.

My questions are:

  1. Why am I getting this error, and logically how can I fix it? (There are several ways to poke the minimal example above to make the error go away, but I can't do those things in my real code.)
  2. Is there a different/better high-level approach to Reading GADTs?
1
What happens if you use deriving instance Read (t r) => Read (U t rep r)? Is that not accepted? Would life be easier if you defined your GADT to have only two parameters, tr and rep, and defined a newtype around it to combine t with r?dfeuer
@dfeuer I don't think that works in my example, but even if it does, it definitely doesn't work in my real code.crockeea
I actually meant Show, not Read. Show is much easier to derive.dfeuer
@dfeuer Since my Read instance has to be manually derived, I figure I have to have a corresponding Show instance, which is certainly not what GHC would generate (even if it could).crockeea

1 Answers

1
votes

Your custom Show instance for Foo doesn't parenthesize correctly.

> U2 $ Foo $ Bar 3
U2Wrap Bar 3

When writing custom Show instances, you should define showsPrec instead. That's because show just gives back a string independently of the context, while showsPrec parenthesizes based on precendence. See Text.Show for further documentation.

instance (Show r) => Show (Foo r) where
 showsPrec n (Foo x) = showsPrec n x

Which works here.

I don't know of an elegant approach that would automacially get us a Read instance for this GADT. The deriving mechanism can't seem to figure out that only a single constructor has to be considered for each rep.

At least Show can be derived even here. I also include here a manual Read instance which I hope conforms to Show. I tried to mimic the definitions in Text.Read, which you could also do in other cases. Alternatively, one could use the -ddump-deriv GHC argument to look at derived Read instances, and try to copy them in custom code.

{-# LANGUAGE GADTs, StandaloneDeriving, FlexibleInstances, FlexibleContexts #-}

import Text.Read
import Data.Proxy

data U t rep r where
  U1 :: t r -> U t Int r
  U2 :: t r -> U t Char r

deriving instance Show (t r) => Show (U t rep r)

instance Read (t r) => Read (U t Int r) where
  readPrec = parens $ do
    prec 10 $ do
      Ident "U1" <- lexP
      U1 <$> readPrec

instance Read (t r) => Read (U t Char r) where
  readPrec = parens $ do
    prec 10 $ do
      Ident "U2" <- lexP
      U2 <$> readPrec