El error que está recibiendo le dice cuál cree que debería ser el tipo; desafortunadamente, ambos tipos se denotan por variables de tipo, lo que hace que sea más difícil de ver. La primera línea dice que usted dio la expresión tipo n
, pero quería darle el tipo n1
. Para averiguar cuáles son éstos, un vistazo a las siguientes líneas:
`n1' is a rigid type variable bound by
the type signature for `width' at Dungeon.hs:11:16
Esto dice que n1
es una variable de tipo cuyo valor es conocido y por lo tanto no se puede cambiar (es "rígido"). Dado que está vinculado por la firma tipo para width
, usted sabe que está vinculado por la línea width :: (Num n) => a -> n
. Hay otro n
en alcance, por lo que este n
se renombra a n1
(width :: (Num n1) => a -> n1
). A continuación, tenemos
`n' is a rigid type variable bound by
the instance declaration at Dungeon.hs:13:14
Esto le está diciendo que Haskell se encontró el tipo n
desde la línea de instance (Num n) => HasArea (Room n) where
.El problema que se informa es que n
, que es el tipo GHC calculado para width (Room w h) = w
, no es lo mismo que n1
, que es el tipo que esperaba.
La razón por la que tiene este problema es porque su definición de width
es menos polimórfica de lo esperado. La firma de tipo width
es (HasArea a, Num n1) => a -> n1
, lo que significa que para cada tipo que es una instancia de HasArea
, puede representar su ancho con cualquier tipo de número. Sin embargo, en la definición de su instancia, la línea width (Room w h) = w
significa que width
tiene el tipo Num n => Room n -> n
. Tenga en cuenta que esto no es suficientemente polimórfico: mientras que Room n
es una instancia de HasArea
, esto requeriría width
para tener el tipo (Num n, Num n1) => Room n -> n1
. Es esta incapacidad para unificar el n
específico con el n1
general que está causando su tipo de error.
Hay varias maneras de arreglarlo. Un enfoque (y probablemente el mejor enfoque), que se puede ver en sepp2k's answer es hacer que HasArea
tome una variable de tipo del tipo * -> *
; esto significa que en lugar de a
siendo un tipo en sí, cosas como a Int
o a n
son tipos. Maybe
y []
son ejemplos de tipos con el tipo * -> *
. (Los tipos ordinarios como Int
o Maybe Double
tienen el tipo *
.) Esta es probablemente la mejor opción.
Si usted tiene algunos tipos de *
tipo que tienen un área (por ejemplo, , data Space = Space (Maybe Character)
, donde el width
es siempre 1
), sin embargo, que no funcionan. Otra forma (que requiere algunas extensiones a Haskell98/Haskell2010) es hacer HasArea
una clase de tipo de múltiples parámetros:
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}
data Room n = Room n n deriving Show
class Num n => HasArea a n where
width :: a -> n
instance Num n => HasArea (Room n) n where
width (Room w h) = w
Ahora, se pasa el tipo de la anchura como un parámetro a la misma clase de tipos, por lo width
tiene el tipo (HasArea a n, Num n) => a -> n
. Una posible desventaja de esto, sin embargo, es que puede declarar instance HasArea Foo Int
y instance HasArea Foo Double
, lo que puede ser problemático. Si es así, para resolver este problema, puede usar dependencias funcionales o familias de tipos. Las dependencias funcionales le permiten especificar que, dado un tipo, los demás tipos están determinados de manera única, como si tuviera una función común. Usando los da el código
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies #-}
data Room n = Room n n deriving Show
class Num n => HasArea a n | a -> n where
width :: a -> n
instance Num n => HasArea (Room n) n where
width (Room w h) = w
El bit dice GHC de que si se puede inferir a
, entonces se puede inferir también n
, ya que sólo hay una por cada n
a
. Esto evita el tipo de casos discutidos anteriormente.
familias de tipos son más diferentes:
{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts, TypeFamilies #-}
data Room n = Room n n deriving Show
class Num (Area a) => HasArea a where
type Area a :: *
width :: a -> Area a
instance Num n => HasArea (Room n) where
type Area (Room n) = n
width (Room w h) = w
Este dice que además de tener una función width
, la clase HasArea
también tiene un Area
tipo (o función de tipo, si quieres pensar en ello de esa manera). Por cada HasArea a
, especifique qué tipo es Area a
(que, gracias a la restricción de superclase, debe ser una instancia de Num
), y luego utilice ese tipo como su tipo de número.
¿En cuanto a cómo depurar errores como este?Honestamente, mi mejor consejo es "Practicar, practicar, practicar". Con el tiempo, se acostumbrará más a descifrar (a) qué dicen los errores, y (b) qué fue lo que probablemente salió mal. Cambiar las cosas al azar es una forma de hacer ese aprendizaje. El mayor consejo que puedo dar, sin embargo, es prestar atención a las líneas Couldn't match expected type `Foo' against inferred type `Bar'
. Estos le dicen qué compilador compiló (Bar
) y esperado (Foo
) para el tipo, y si puede determinar con precisión qué tipos son, eso lo ayuda a descubrir dónde está el error.
Guau, realmente lo dejaste fuera del parque. Estaba buscando en Google estos temas exactos porque de inmediato vi la falta de capacidad para usar esto para tipos con varios tipos. No solo eso, me perdí las clases de tipos multi param, que es con mucho el más fácil de todos. Gracias. –
+1 Creo que la clase de tipos multiparámetro + fundeps es el enfoque más directo aquí. –
+1 ¡Gran respuesta! – asm