tutorial - haskell simbolos
Cómo escribo, "si typeclass a, entonces a también es una instancia de b según esta definición". (6)
(Editar: dejando el cuerpo para la posteridad, pero salta hasta el final para encontrar la solución real)
En la instance MyClass a => Show a
declaración instance MyClass a => Show a
, examinemos el error "Restricción no es menor que el encabezado de instancia". La restricción es la restricción de clase de tipo a la izquierda de ''=>'', en este caso MyClass a
. El "encabezado de instancia" es todo después de la clase para la que está escribiendo una instancia, en este caso a
(a la derecha de Show
). Una de las reglas de inferencia de tipo en GHC requiere que la restricción tenga menos constructores y variables que el encabezado. Esto es parte de lo que se llama las " Condiciones de Paterson ". Estos existen como una garantía de que la verificación de tipos termina.
En este caso, la restricción es exactamente la misma que la cabeza, es decir, a
, por lo que no pasa esta prueba. Puede eliminar las comprobaciones de condición de Paterson habilitando las UndecidableInstances , muy probablemente con el {-# LANGUAGE UndecidableInstances #-}
pragma.
En este caso, esencialmente está utilizando su clase MyClass
como sinónimo de clase de clase para la clase Show
. Crear sinónimos de clase como este es uno de los usos canónicos de la extensión UndecidableInstances, por lo que puede usarlo de forma segura aquí.
''Indecidible'' significa que GHC no puede probar que la verificación de tipo terminará. Aunque parezca peligroso, lo peor que puede pasar al habilitar las UndecidableInstances es que el compilador realizará un bucle, que finalmente terminará después de agotar la pila. Si se compila, entonces obviamente la verificación de tipo terminó, por lo que no hay problemas. La extensión peligrosa es IncoherentInstances, que es tan mala como suena.
Editar: otro problema hecho posible por este enfoque surge de esta situación:
instance MyClass a => Show a where
data MyFoo = MyFoo ... deriving (Show)
instance MyClass MyFoo where
Ahora hay dos instancias de Show for MyFoo
, la de la cláusula derivada y la de las instancias de MyClass. El compilador no puede decidir cuál usar, por lo que rescatará con un mensaje de error. Si intentas hacer que las instancias de MyClass
de tipos que no controlas tengan instancias de Show
, tendrás que usar newtypes para ocultar las instancias Show existentes. Incluso los tipos sin instancias MyClass
seguirán en conflicto porque la instance MyClass => Show a
definición instance MyClass => Show a
porque la definición realmente proporciona una implementación para todos los posibles a
(la verificación de contexto aparece más adelante, no está involucrada con la selección de instancia)
Así que ese es el mensaje de error y cómo UndecidableInstances lo hace desaparecer. Desafortunadamente, es un gran problema usarlo en el código real, por razones que Edward Kmett explica. El ímpetu original fue evitar especificar una restricción Show
cuando ya hay una restricción MyClass
. Dado que, lo que haría es usar myShow
de MyClass
lugar de show
. No necesitarás la restricción Show
en absoluto.
Tengo una clase de tipo MyClass
, y hay una función en ella que produce una String
. Quiero usar esto para dar a entender una instancia de Show
, para poder pasar los tipos que implementan MyClass
para show
. Hasta ahora tengo,
class MyClass a where
someFunc :: a -> a
myShow :: a -> String
instance MyClass a => Show a where
show a = myShow a
que da el error Constraint is no smaller than the instance head.
También intenté,
class MyClass a where
someFunc :: a -> a
myShow :: a -> String
instance Show (MyClass a) where
show a = myShow a
que da el error, Class
MyClass ''usado como un tipo''.
¿Cómo puedo expresar correctamente esta relación en Haskell? Gracias.
Debo añadir que deseo hacer un seguimiento de esto con instancias específicas de MyClass
que emiten cadenas específicas en función de su tipo. Por ejemplo,
data Foo = Foo
data Bar = Bar
instance MyClass Foo where
myShow a = "foo"
instance MyClass Bar where
myShow a = "bar"
main = do
print Foo
print Bar
Como señaló Ed Kmett, esto no es posible en absoluto para su caso. Sin embargo, si tiene acceso a la clase para la que desea proporcionar una instancia predeterminada, puede reducir la repetición mínima con una implementación predeterminada y restringir el tipo de entrada con la firma predeterminada que necesita:
{-# LANGUAGE DefaultSignatures #-}
class MyClass a where
someFunc :: a -> Int
class MyShow a where
myShow :: a -> String
default myShow :: MyClass a => a -> String
myShow = show . someFunc
instance MyClass Int where
someFunc i = i
instance MyShow Int
main = putStrLn (myShow 5)
Tenga en cuenta que la única repetición real (bueno, aparte de todo el ejemplo) se reduce a la instance MyShow Int
.
Vea a aeson
s ToJSON
para un ejemplo más realista.
Creo que sería mejor hacerlo al revés:
class Show a => MyClass a where
someFunc :: a -> a
myShow :: MyClass a => a -> String
myShow = show
Deseo discrepar enérgicamente con las soluciones rotas planteadas hasta ahora.
instance MyClass a => Show a where
show a = myShow a
Debido a la forma en que funciona la resolución de instancia, ¡esta es una instancia muy peligrosa para correr!
La resolución de la instancia avanza de manera efectiva con la concordancia de patrones en el lado derecho de cada instancia =>
, completamente sin tener en cuenta lo que está a la izquierda de =>
.
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 TODAS las instancias de Show. 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 hacerlo compilar, obtendrá fallas no tan sutiles cuando vaya a escribir módulos que importen el módulo que proporciona esta definición y necesita usar 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 lo piensas dado:
instance MyClass a => Show a where
show = myShow
instance HisClass a => Show a where
show = hisShow
¿Cuál debería elegir 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 en su derecho de ignorar su instancia e intentar usar la suya.
La respuesta correcta, por desgracia, es hacer dos cosas.
Para cada instancia individual de MyClass, puede definir una instancia correspondiente de Show con la definición muy mecánica
instance MyClass Foo where ...
instance Show Foo where
show = myShow
Esto es bastante desafortunado, pero funciona bien cuando hay pocas instancias de MyClass en consideración.
Cuando tiene una gran cantidad 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 nuevo tipo como un vehículo, por ejemplo, despacho. y entonces
instance Foo a => Show (WrappedFoo a) where ...
instance Bar a => Show (WrappedBar a) where ...
es inequívoco, porque el tipo ''patrones'' para WrappedFoo a
y WrappedBar a
son disjuntos.
Hay una serie de ejemplos de este modismo corriendo en el paquete base
.
En Control.Applicative hay definiciones para WrappedMonad
y WrappedArrow
por esta misma razón.
Idealmente, podrías decir:
instance Monad t => Applicative t where
pure = return
(<*>) = ap
pero efectivamente lo que esta instancia está diciendo es que cada Aplicativo debería derivarse primero encontrando una instancia para Monad, y luego enviándola a ella. Entonces, si bien tendría la intención de decir que cada Mónada es Aplicativa (por cierto, la implicación-como =>
lee) lo que realmente dice es que cada Aplicativo es una Mónada, porque tener una cabeza de ejemplo ''t'' concuerda con cualquier tipo. En muchos sentidos, la sintaxis para las definiciones de ''instancia'' y ''clase'' es al revés.
Puede compilarlo, pero no con Haskell 98, debe habilitar algunas extensiones de idioma:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
-- at the top of your file
Las instancias flexibles están ahí para permitir el contexto en la declaración de instancia. Realmente no sé el significado de IndecidiblesInstancias, pero evitaría todo lo que pueda.
Puede encontrar algunas respuestas interesantes en una pregunta de SO relacionada: Vinculación / combinación de clases de tipos en Haskell