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