Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Either derives Show but Maybe does not?

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?

like image 313
gxtaillon Avatar asked Jan 07 '15 22:01

gxtaillon


2 Answers

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.

like image 102
Tikhon Jelvis Avatar answered Oct 19 '22 02:10

Tikhon Jelvis


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.

like image 37
Jonathan Cast Avatar answered Oct 19 '22 03:10

Jonathan Cast