I have a GADT like
data GADT where
Data :: a -> GADT
and I want to implement a Show
instance depending on where Show a
exists.
Intuitively, something like
instance Show GADT where
show (Data a) = if isShow a then show a else "UNSHOWABLE"
Is this actually expressible in Haskell? I do use Typeable a
in my actual GADT, so I could use that if it helps.
You said "depending on where Show a
exists", but I assume this means "depending on whether Show a
exists". If so, then the answer is no, Haskell does not let you write code that does one thing if a type has a particular type class instance and another thing if it doesn't.
You can, however, define an overlappable, orphan Show
instance to serve as a default, like so:
instance {-# OVERLAPPABLE #-} Show a where
show _ = "UNSHOWABLE"
If you define your GADT with a Show
constraint:
data GADT where
Data :: (Show a) => a -> GADT
then you can define its Show
instance like so:
instance Show GADT where
show (Data a) = show a
and it "works", after a fashion:
λ> show (Data 'a')
"'a'"
λ> show (Data id)
"UNSHOWABLE"
But, there's no legitimate way to isolate this behavior to your GADT
. The default Show
instance will be available to non-GADT types as well:
λ> show 'a'
"'a'"
λ> show id
"UNSHOWABLE"
Many people think they ought to be able to write something like the following (maybe with overlapping instances):
class MyShow a where
myShow :: a -> String
instance (Show a) => MyShow a where
myShow = show
instance MyShow a where
myShow _ = "UNSHOWABLE"
But this doesn't work. GHC doesn't decide whether an instance applies or not based on its constraints, so it interprets these as two conflicting definitions for the same set of types (i.e., all types a
) and refuses to compile this code, even with a liberal sprinkling of {-# OVERLAPS #-}
pragmas.
If you are willing to explicitly enumerate types (either the types that should be "UNSHOWABLE"
or the types that are Show
able), then you can construct MyShow
class with valid overlapping instances:
-- Example enumerating the showable types (Int and Char)
class MyShow a where myShow :: a -> String
instance {-# OVERLAPPABLE #-} MyShow a where myShow _ = "UNSHOWABLE"
instance MyShow Int where myShow = show
instance MyShow Char where myShow = show
-- Example enumerating the unshowable types (a -> b)
class MyShow' a where myShow' :: a -> String
instance {-# OVERLAPPABLE #-} (Show a) => MyShow' a where myShow' = show
instance MyShow' (a -> b) where myShow' _ = "UNSHOWABLE"
You may think this is a totally unacceptable approach, but I encourage you to try it in a real program. You may discover that there simply aren't that many types that need to be enumerated, and the boilerplate just isn't all that burdensome.