2015-01-07 37 views
6

La documentazione di entrambi Either e Maybe indica che hanno istanze di Show.Perché uno dei due deriva Show ma Maybe no?

Either è definito come derivante Show, semplicemente:

data Either a b = Left a | Right b 
    deriving (Eq, Ord, Read, Show, Typeable) 

Tuttavia, Maybe non:

data Maybe a = Nothing | Just a 
    deriving (Eq, Ord) 

Poiché sono parte di base e sono così simili perché non Maybe deriva direttamente Show?

Un'altra domanda potrebbe anche essere, dove ottiene l'istanza Show?

risposta

10

L'istanza per Maybe è definita esplicitamente in GHC.Show, insieme alle istanze per un gruppo di altri tipi comuni come le tuple. È possibile scoprire dove un'istanza è stata definita utilizzando il comando :i 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’ 

non so il motivo per cui l'istanza definite in modo esplicito o metterlo in GHC.Show invece di Data.Maybe -come per quanto posso dire, potrebbe essere spostato su Data.Maybe e/o derivato. La mia ipotesi è che non volessero che Data.Maybe dipendesse da qualsiasi cosa eccetto GHC.Base (come ora), presumibilmente perché è usato in alcuni degli altri moduli principali.

4

Le tuple AFAIK non sono definite da nessuna parte, quindi per evitare istanze orfane [1] è necessario definire le istanze Mostra tuple in GHC.Show [2]. L'attuazione di quei casi succede utilizzare foldr1:

show_tuple :: [ShowS] -> ShowS 
show_tuple ss = showChar '(' 
       . foldr1 (\s r -> s . showChar ',' . r) ss 
       . showChar ')' 

così GHC.Show importa GHC.List cui si definisce tale funzione. GHC.List, a sua volta, definisce lookup, che è nella monade Maybe (il buon vecchio pregiudizio del monomorfismo di Haskell 98 immagino). Quindi GHC.List importa Data.Maybe. Per definire un'istanza Show, Data.Maybe dovrebbe importare GHC.Show (direttamente o indirettamente), che renderebbe l'intera sequenza GHC.Show -> GHC.List -> Data.Maybe -> GHC.Show una circolare dipendenza. GHC non supporta le dipendenze circolari molto bene (non che siano facili da supportare!), Quindi la base funziona davvero molto per evitarli.

[1] Un'istanza orfana è definita in un modulo diverso rispetto alla classe e al tipo coinvolto nell'istanza. Formalmente, Haskell richiede che la ricerca dell'istanza venga eseguita in qualsiasi modulo direttamente o indirettamente importato dal modulo in fase di compilazione; ma per le istanze non orfane GHC può cortocircuitarlo e guardare solo in due punti. Per le istanze orfane, deve tracciare ogni istanza orfana in un modulo e quindi tracciare che tali istanze vengono esposte di nuovo da ogni modulo che le importa, che è più costoso (e significa che deve mantenere un ambiente di contesto con potenzialmente molte istanze che non sono nemmeno rilevanti per il modulo corrente, perché in realtà non importa quelle classi o tipi). Quindi, una buona pratica è evitare le istanze orfane per questo motivo.

Un po 'più filosoficamente, le istanze orfani sono un ottimo modo per ottenere due istanze contrastanti della stessa classe/tipo nel vostro programma, che dal momento che sono entrambi 'visibili' nel vostro modulo Main significa che entreranno in conflitto. Quindi la caratteristica del linguaggio stesso è un po 'dubbia.

[2] IIRC GHC fornisce solo Show casi sino ad un (relativamente piccolo) numero fisso di componenti tuple, che non è abbastanza Haskell 98 conforme ma è sufficiente per qualsiasi esigenza pratico programmazione. (Seriamente, non usare tuple con più di 3 elementi in ogni caso, si dimenticare quali componenti specifici significano). Non so se lo standard è stato aggiornato per portare GHC alla conformità negli ultimi anni o meno.