2011-12-16 11 views
11

Jugando con las clases de tipo I ocurrió la aparentemente inocenteinstancia clase de tipos de dependencias funcionales no funciona

class Pair p a | p -> a where 
    one :: p -> a 
    two :: p -> a 

Esto parece funcionar bien, por ejemplo,

instance Pair [a] a where 
    one [x,_] = x 
    two [_,y] = y 

Sin embargo, me encontré en problemas para las tuplas. A pesar de que la siguiente definición compila ...

instance Pair (a,a) a where 
    one p = fst p 
    two p = snd p 

... no puedo utilizarlo como yo esperaba:

main = print $ two (3, 4) 

No instance for (Pair (t, t1) a) 
    arising from a use of `two' at src\Main.hs:593:15-23 
Possible fix: add an instance declaration for (Pair (t, t1) a) 
In the second argument of `($)', namely `two (3, 4)' 
In the expression: print $ two (3, 4) 
In the definition of `main': main = print $ two (3, 4) 

¿Hay una manera de definir la instancia correcta? ¿O tengo que recurrir a un contenedor newtype?

Respuesta

18

Su instancia funciona muy bien, en realidad. Observe:

main = print $ two (3 :: Int, 4 :: Int) 

Esto funciona como se esperaba. Entonces, ¿por qué no funciona sin la anotación tipo, entonces? Bien, considere el tipo de tupla: (3, 4) :: (Num t, Num t1) => (t, t1). Debido a que los literales numéricos son polimórficos, nada requiere que sean del mismo tipo. La instancia se define para (a, a), pero la existencia de esa instancia no le indicará a GHC que unifique los tipos (por una variedad de buenas razones). A menos que GHC pueda deducir por otros medios que los dos tipos son iguales, no elegirá la instancia que desee, incluso si los dos tipos podrían hacerse iguales.

Para resolver su problema, podría simplemente agregar anotaciones de tipo, como hice anteriormente. Si los argumentos provienen de otro lado, generalmente no es necesario porque ya se sabe que son del mismo tipo, pero se vuelve torpe rápidamente si se quieren usar literales numéricos.

Una solución alternativa es observar que, debido a la forma en que funciona la selección de instancia, tener una instancia para (a, a) significa que no puede escribir una instancia como (a, b), incluso si así lo desea. Así podemos engañar un poco, para forzar la unificación con la clase de tipo, así:

instance (a ~ b) => Pair (a,b) a where 

que necesita la extensión TypeFamilies para el contexto ~, creo. Lo que hace es permitir que la instancia coincida con cualquier tupla al principio, porque la selección de instancia ignora el contexto. Sin embargo, después de elegir la instancia, el contexto a ~ b afirma la igualdad de tipo, que producirá un error si son diferentes pero, lo que es más importante aquí, unificará las variables de tipo si es posible. Al usar esto, su definición de main funciona como está, sin anotaciones.

+0

¡Gracias, muy interesante! – Landei

6

El problema es que un número literal tiene un tipo polimórfico. No es obvio para el contador de tipos que ambos literales deben tener el mismo tipo (Int). Si utilizas algo que no es polimórfico para tus tuplas, tu código debería funcionar. Considere estos ejemplos:

*Main> two (3,4) 

<interactive>:1:1: 
    No instance for (Pair (t0, t1) a0) 
     arising from a use of `two' 
    Possible fix: add an instance declaration for (Pair (t0, t1) a0) 
    In the expression: two (3, 4) 
    In an equation for `it': it = two (3, 4) 
*Main> let f = id :: Int -> Int -- Force a monomorphic type 
*Main> two (f 3,f 4) 
4 
*Main> two ('a','b') 
'b' 
*Main> two ("foo","bar") 
"bar" 
*Main> two (('a':),('b':)) "cde" 
"bcde" 
*Main> 
Cuestiones relacionadas