2012-05-07 17 views
5

Al tratar de utilizar Data.Has, he estado escribiendo código como el siguiente:familias de tipos de Haskell, entender el mensaje de error

data Name = Name; type instance TypeOf Name = Text 
type NameRecord = FieldOf Name; 

que he encontrado:

instance I NameRecord where 
    ... 

genera un error de compilación, a saber:

Illegal type synonym family application in instance

Considerando lo siguiente:

instance (NameRecord ~ a) => I a where 
    ... 

Compila bien.

Creo que el error está relacionado con el ticket this en GHC, marcado como no válido.

La respuesta a la entrada dice:

I am not sure what you are suggesting. We cannot automatically transform

instance C (Fam Int) -- (1) 

into

instance (Fam Int ~ famint) => C famint -- (2) 

This works if there is only one instance, but as soon as there are two such instances, they always overlap.

Maybe you are suggesting that we should do it anyway and programmers should just take the implicit transformation into account. I don't think that this is a good idea. It's confusing for very little benefit (as you can always write the transformed instance yourself with little effort).

Puede alguien más detalles sobre esta explicación, tal vez con un código de ejemplo en el que (1) falla pero (2) no lo hace, y por qué?

Respuesta

4

Un caso donde (1) falla pero (2) no es trivial; porque sinónimos (type ExampleOfATypeSynonym = ...) de tipo no están permitidos en declaraciones de instancias, pero están permitidos en las limitaciones, cualquier situación en la que sólo tiene una ejemplo a esto:

-- (1) 
class Foo a 
type Bla =() 
instance Foo Bla 

... se puede transformar en:

-- (2) 
class Foo a 
type Bla =() 
instance (a ~ Bla) => Foo a 

La única razón por la que (1) no se debe a que los sinónimos de tipo no están permitidos en declaraciones de instancias, y eso es porque el tipo sinónimos son como funciones de tipo: proporcionan un mapeo de un solo sentido de un nombre de tipo de un nombre de tipo, así que si tiene un type B = A y un instance Foo B, no es obvio que en su lugar se cree una instancia de Foo A. La regla existe para que tenga que escribir instance Foo A en su lugar para dejar en claro que que es el tipo que realmente obtiene la instancia.

El uso de familias de tipos es irrelevante en este contexto, porque el problema es más bien que está utilizando un tipo sinónimo, el tipo NameRecord. También debe tener en cuenta que si se elimina el sinónimo de tipo y se reemplaza por FieldOf Name directamente, la compilación seguirá fallando; esto se debe a que una "familia de tipos" es solo una versión mejorada de sinónimos de tipo, por lo que FieldOf Name también es un "sinónimo de tipo" para Name :> Text en este contexto. En su lugar, debe usar una familia de datos y una instancia de datos para obtener una asociación "bidireccional".

Se puede encontrar más información sobre las familias de datos en el GHC documentation.


Creo que significa "... donde (2) falla pero (1) no ..."

Imaginemos que tenemos un tipo de clase de este modo:

class Foo a where 
    foo :: a 

Ahora, puede escribir casos de este modo:.

instance Foo Int where 
    foo = 0 

instance Foo Float where 
    foo = 0 

main :: IO() 
main = print (foo :: Float) 

Esto funciona como cabría esperar Sin embargo, si transformar el código en este:

{-# LANGUAGE FlexibleInstances, TypeFamilies #-} 
class Foo a where 
    foo :: a 

instance (a ~ Int) => Foo a where 
    foo = 0 

instance (a ~ Float) => Foo a where 
    foo = 0 

main :: IO() 
main = print (foo :: Float) 

no compila, sino que muestra el error:

test.hs:5:10: 
    Duplicate instance declarations: 
     instance a ~ Int => Foo a -- Defined at test.hs:5:10-27 
     instance a ~ Float => Foo a -- Defined at test.hs:8:10-29 

Por lo tanto, este es el ejemplo que con suerte estaba buscando. Ahora, esto solo ocurre si hay más de una instancia de Foo que usa este truco. ¿Porqué es eso?

Cuando GHC resuelve clases de tipos, que sólo se ve en la cabecera declaración de la instancia; es decir, ignora todo antes del =>. Cuando ha elegido una instancia, se "compromete" con ella y comprueba las restricciones antes de => para ver si son verdaderas. Por lo tanto, al principio ve dos instancias:

instance Foo a where ... 
instance Foo a where ... 

Es claramente imposible decidir qué instancia usar basándose solo en esta información.

+0

Fuera de interés, ¿sabe si el método de GHC para resolver clases de tipos es una decisión de diseño explícita? Y si es así, ¿por qué? (¿O tal vez la alternativa es demasiado complicada para implementarse limpiamente?) – huon

+3

La razón es que el Informe Haskell requiere que se comporte de esta manera. La razón de ** que ** es que si no se comportara así, tendría que haber un algoritmo que diera una heurística para "qué tan bien un tipo se ajusta a una restricción"; tendrías que discutir "Sí, este tipo se ajusta a esta instancia de clase, pero esa otra instancia se ajusta mucho mejor porque {menos restricciones, distancia de reducción más corta, ...}". Podría ser posible desarrollar dicha heurística, pero rompería la Asunción del Mundo Abierto, que es un concepto clave cuando se trata de clases de tipos. – dflemstr

+0

Imagine 'instance String ~ a => Foo a' y' instancia a ~ [b] => Foo a'. Ese es un ejemplo de casos en los que necesitarías un algoritmo para resolver 'Foo [Char]'. – dflemstr