deseo de estar en desacuerdo con fuerza con las soluciones rotos planteaba hasta ahora.
instance MyClass a => Show a where
show a = myShow a
Debido a la forma en que funciona la resolución de la instancia, esta es una instancia muy peligrosa para correr!
La resolución de la instancia se realiza por coincidencia de patrones efectiva en el lado derecho de cada instancia de =>
, sin tener en cuenta lo que está a la izquierda del =>
.
Cuando ninguna de esas instancias se superpone, esto es algo hermoso. Sin embargo, lo que está diciendo aquí es "Aquí hay una regla que debe usar para CADA Mostrar instancia. Cuando se le pida una instancia de show para cualquier tipo, necesitará una instancia de MyClass, así que consiga eso, y aquí está la implementación." - una vez que el compilador se ha comprometido a elegir usar su instancia, (solo en virtud del hecho de que 'a' se unifica con todo) ¡no tiene ninguna posibilidad de retroceder y usar cualquier otra instancia!
Si activa {-# LANGUAGE OverlappingInstances, IncoherentInstances #-}
, etc. para compilarlo, obtendrá fallas no tan sutiles cuando vaya a escribir módulos que importen el módulo que proporciona esta definición y que necesitan utilizar cualquier otra instancia de Show. En última instancia, podrás obtener este código para compilar con suficientes extensiones, ¡pero lamentablemente no hará lo que crees que debería hacer!
Si se piensa en ello da:
instance MyClass a => Show a where
show = myShow
instance HisClass a => Show a where
show = hisShow
la que debe recoger el compilador?
Su módulo solo puede definir uno de estos, pero el código de usuario final importará un grupo de módulos, no solo el suyo.Además, si otro módulo define
instance Show HisDataTypeThatHasNeverHeardOfMyClass
el compilador estaría bien dentro de sus derechos a ignorar a su instancia y tratar de usar el suyo.
La respuesta correcta, por desgracia, es hacer dos cosas.
Para cada individuo instancia de MyClass puede definir una instancia correspondiente del Show con la definición muy mecánico
instance MyClass Foo where ...
instance Show Foo where
show = myShow
Esto es bastante desafortunado, pero funciona bien cuando hay sólo unos pocos casos de MiClase menores consideración.
Cuando tiene un gran número de instancias, la forma de evitar la duplicación de código (para cuando la clase es considerablemente más complicada que mostrar) es definir.
newtype WrappedMyClass a = WrapMyClass { unwrapMyClass :: a }
instance MyClass a => Show (WrappedMyClass a) where
show (WrapMyClass a) = myShow a
Esto proporciona el newtype como vehículo para despacho de instancia. y luego
instance Foo a => Show (WrappedFoo a) where ...
instance Bar a => Show (WrappedBar a) where ...
es inequívoca, debido a que los 'patrones' de tipo para WrappedFoo a
y WrappedBar a
son disjuntos.
Hay una serie de ejemplos de este modismo en el paquete base
.
En Control.Applicative hay definiciones para WrappedMonad
y WrappedArrow
por esta misma razón.
Lo ideal sería que usted sería capaz de decir:
instance Monad t => Applicative t where
pure = return
(<*>) = ap
pero con eficacia este caso lo que está diciendo es que cada Aplicativo debe ser derivado al encontrar primero una instancia para la Mónada, y luego despachar a ella. Entonces, si bien tendría la intención de decir que cada mónada es aplicable (por cierto, como dice la implicación, como =>
), lo que dice es que cada aplicante es una mónada, porque tener un encabezado de instancia 't' coincide con cualquier tipo. En muchos sentidos, la sintaxis para las definiciones de 'instancia' y 'clase' es al revés.
FYI He respondido esta pregunta porque es detallada pero concisa, clara y bien formada. Una pregunta modelo, y la recitaría nuevamente si fuera posible. –