2011-01-11 10 views
11

Entiendo bastante bien el 3/4 del resto del lenguaje, pero cada vez que uso mis clases de una manera significativa en mi código, me atrinchero firmemente. .Parece que no puedo entender las variables de tipo mezcladas con las clases

¿Por qué no funciona este código extremadamente simple?

data Room n = Room n n deriving Show 

class HasArea a where 
    width :: (Num n) => a -> n 

instance (Num n) => HasArea (Room n) where 
    width (Room w h) = w 

Por lo tanto, el ancho de la habitación se denota por enteros o tal flota, no quiero restringirlo en este punto. Tanto la clase y la instancia restringen el tipo n a Nums, pero todavía no le gusta y me sale este error:

Couldn't match expected type `n1' against inferred type `n' 
    `n1' is a rigid type variable bound by 
     the type signature for `width' at Dungeon.hs:11:16 
    `n' is a rigid type variable bound by 
     the instance declaration at Dungeon.hs:13:14 
In the expression: w 
In the definition of `width': width (Room w h) = w 
In the instance declaration for `HasArea (Room n)' 

Por lo que me dice que los tipos no coinciden, pero no es así dime qué tipos cree que son, lo que sería realmente útil. Como nota al margen, ¿hay alguna manera fácil de solucionar un error como este? La única forma en que sé hacerlo es cambiar aleatoriamente las cosas hasta que funcionen.

Respuesta

18

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 na. 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 Areatipo (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.

+0

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. –

+0

+1 Creo que la clase de tipos multiparámetro + fundeps es el enfoque más directo aquí. –

+0

+1 ¡Gran respuesta! – asm

9
class HasArea a where 
    width :: (Num n) => a -> n 

El tipo (Num n) => a -> n significa que para cualquier tipo n que es una instancia de Num, width debe ser capaz de devolver un valor de ese tipo. Así que para cualquier valor de tipo vT, donde T es una instancia de HasArea el siguiente código debe ser válido:

let x :: Integer = width v 
    y :: Double = width v 
in 
    whatever 

Sin embargo, este no es el caso para sus Room casos. Por ejemplo, Room Integery :: Dobule = width v no es válido.

Para que su ejemplo funciona se podría hacer algo como esto:

data Room n = Room n n deriving Show 

class HasArea a where 
    width :: (Num n) => a n -> n 

instance HasArea Room where 
    width (Room w h) = w 

Aquí no decimos que Room Integer, Room Float etc., son ejemplos de HasArea, pero en lugar Room es una instancia de HasArea. Y el tipo de width es tal que produce un valor de tipo n cuando se le da un valor de tipo a n, por lo que si pone un Room Integer, obtiene un Integer. De esta manera el tipo encaja.

+0

Ya veo, gracias. Eso tiene sentido. –

Cuestiones relacionadas