Documentation of both Either
and Maybe
indicate that they have instances of Show
.
Either
is defined as deriving Show
, simply :
data Either a b = Left a | Right b
deriving (Eq, Ord, Read, Show, Typeable)
Yet, Maybe
does not :
data Maybe a = Nothing | Just a
deriving (Eq, Ord)
Since they are part of base
and are so similar why doesn't Maybe
directly derive Show
?
Another question might also be, where does it get its Show
instance?
The instance for Maybe
is defined explicitly in GHC.Show
, along with instances for a whole bunch of other common types like tuples. You can find out where an instance was defined using the :i
command in ghci
:
Prelude> :i Maybe
data Maybe a = Nothing | Just a -- Defined in ‘Data.Maybe’
instance Eq a => Eq (Maybe a) -- Defined in ‘Data.Maybe’
instance Monad Maybe -- Defined in ‘Data.Maybe’
instance Functor Maybe -- Defined in ‘Data.Maybe’
instance Ord a => Ord (Maybe a) -- Defined in ‘Data.Maybe’
instance Read a => Read (Maybe a) -- Defined in ‘GHC.Read’
instance Show a => Show (Maybe a) -- Defined in ‘GHC.Show’
I don't know why they defined the instance explicitly or put it in GHC.Show
instead of Data.Maybe
—as far as I can tell, it could be moved to Data.Maybe
and/or derived. My guess is that they did not want Data.Maybe
to depend on anything except GHC.Base
(as it does now), presumably because it's used in some of the other core modules.
AFAIK tuples aren't defined anywhere, so to avoid orphan instances[1] the Show instances for tuples have to be defined in GHC.Show[2]. The implementation of those instances happens to use foldr1
:
show_tuple :: [ShowS] -> ShowS
show_tuple ss = showChar '('
. foldr1 (\s r -> s . showChar ',' . r) ss
. showChar ')'
so GHC.Show imports GHC.List where that function is defined. GHC.List, in turn, defines lookup
, which is in the Maybe
monad (good old Haskell 98's monomorphism bias I guess). So GHC.List imports Data.Maybe. In order to define a Show
instance, Data.Maybe would need to import GHC.Show (directly or indirectly), which would make the whole sequence GHC.Show -> GHC.List -> Data.Maybe -> GHC.Show a circular dependency. GHC doesn't support circular dependencies very well (not that they're easy to support!), so base works really hard to avoid them.
[1] An orphan instance is one defined in a different module than both the class and the type involved in the instance. Formally, Haskell requires instance search to be done in any module directly or indirectly imported by the module being compiled; but for non-orphan instances GHC can short-circuit that and just look in two places. For orphan instances, it has to track every orphan instance in a module and then track that those instances are re-exposed by every module that imports them, which is more expensive (and means it has to keep a context environment with potentially many instances that aren't even relevant to the current module, because it doesn't actually import those classes or types). So good practice is to avoid orphan instances for that reason.
Somewhat more philosophically, orphan instances are a really good way to get two conflicting instances of the same class / type in your program, which since they are both 'visible' in your Main
module means they will conflict. So the language feature itself is kind of dodgy.
[2] IIRC GHC only provides Show
instances up to a (relatively small) fixed number of tuple components, which isn't quite Haskell 98 compliant but is good enough for any practical programming need. (Seriously, don't use tuples with more than 3 elements anyway, you will forget what specific components mean). I don't know if the standard has been updated to bring GHC into compliance in the last few years or not.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With