Vorrei dissentire energicamente con le soluzioni rotti poste finora.
A causa del modo in cui la risoluzione dell'istanza funziona, questa è un'istanza molto pericolosa da avere in giro!
La risoluzione di istanza procede efficacemente sulla corrispondenza del modello sul lato destro di =>
di ciascuna istanza, completamente senza riguardo a ciò che si trova sulla sinistra dello =>
.
Quando nessuna di queste istanze si sovrappone, questa è una cosa meravigliosa. Tuttavia, quello che stai dicendo qui è "Ecco una regola da utilizzare per OGNI Mostra istanza. Quando viene richiesta un'istanza di presentazione per qualsiasi tipo, è necessaria un'istanza di MyClass, quindi vai a prenderla, e qui è l'implemento." - una volta che il compilatore si è impegnato a scegliere di utilizzare l'istanza, (solo in virtù del fatto che "a" si unifica con tutto) non ha alcuna possibilità di ricorrere e utilizzare altre istanze!
Se si attiva {-# LANGUAGE OverlappingInstances, IncoherentInstances #-}
, ecc. Per renderlo compilato, si otterranno errori non così sottili quando si va a scrivere moduli che importano il modulo che fornisce questa definizione e devono utilizzare qualsiasi altra istanza Show. Alla fine sarai in grado di compilare questo codice con abbastanza estensioni, ma purtroppo non farà ciò che pensi che dovrebbe fare!
Se ci pensate dato:
instance MyClass a => Show a where
show = myShow
instance HisClass a => Show a where
show = hisShow
che dovrebbe prendere il compilatore?
Il modulo può solo definire uno di questi, ma il codice utente finale importa un mucchio di moduli, non solo il tuo.Inoltre, se un altro modulo definisce
instance Show HisDataTypeThatHasNeverHeardOfMyClass
il compilatore sarebbe andato bene nel suo pieno diritto di ignorare la sua istanza e provare a utilizzare il vostro.
La risposta giusta, purtroppo, è fare due cose.
Per ogni individuale istanza di MyClass è possibile definire una corrispondente istanza di Show con la definizione molto meccanico
instance MyClass Foo where ...
instance Show Foo where
show = myShow
Questo è abbastanza spiacevole, ma funziona bene quando ci sono solo pochi esempi di MyClass sotto considerazione.
Quando si dispone di un numero elevato di istanze, è necessario definire il modo per evitare la duplicazione del codice (per quando la classe è notevolmente più complicata di mostrare).
newtype WrappedMyClass a = WrapMyClass { unwrapMyClass :: a }
instance MyClass a => Show (WrappedMyClass a) where
show (WrapMyClass a) = myShow a
Ciò fornisce il newtype come un veicolo per esempio la spedizione. e poi
instance Foo a => Show (WrappedFoo a) where ...
instance Bar a => Show (WrappedBar a) where ...
è univoca, perché il tipo 'pattern' per WrappedFoo a
e WrappedBar a
sono disgiunti.
Nel pacchetto base
esistono diversi esempi di questo idioma.
In Control.Applicativo ci sono definizioni per WrappedMonad
e WrappedArrow
proprio per questo motivo.
Idealmente si sarebbe in grado di dire:
instance Monad t => Applicative t where
pure = return
(<*>) = ap
ma in modo efficace ciò che questo caso sta dicendo è che ogni applicativo deve essere derivato trovando prima un'istanza per la Monade, e poi l'invio ad esso. Così, mentre avrebbe l'intenzione di dire che ogni Monade è Applicativa (dal modo in cui si legge l'implicazione =>
) ciò che in realtà dice è che ogni Applicativo è una Monade, perché avere un'istanza di testa 't' corrisponde a qualsiasi tipo. In molti modi, la sintassi per le definizioni di 'istanza' e 'classe' è al contrario.
FYI Ho letto questa domanda perché è dettagliata ma concisa, chiara e ben formata. Una domanda modello, e lo farei di nuovo se possibile. –