listas - ¿Por qué Haskell no permite escribir sinónimos cuando declara instancias de clase?
listas haskell (3)
Si bien sé que hay una extensión TypeSynonymInstances en GHC, no tengo idea de cuán "peligrosa" es y me pregunto si esta restricción es arbitraria, algo así como la restricción de monomorfismo, o si existen razones más profundas para ello.
Antes se permitía, pero en un intento de hacer que Haskell estuviera menos lleno de sorpresas para los principiantes, estaba prohibido.
Como lo entiendo, es algo así como la restricción del monomorfismo: no hay nada de malo en deshacerse de ella, pero te abre a un comportamiento que podrías no esperar. Al igual que la restricción de monomorfismo no daña nada, todos los tipos siguen siendo válidos, esto también debería ser totalmente seguro: de todos modos, hay restricciones en los sinónimos de tipo que les impiden hacer algo más elegante que el simple acortamiento de nombres ( por ejemplo , nunca puede aplíquelos parcialmente, de modo que no obtengamos lambdas de nivel de tipo), por lo que siempre puede reemplazarlos con el lado derecho de su definición. Por lo tanto, dado que los lados derechos de esas definiciones pueden verificarse como encabezados de instancia (o contienen más sinónimos de tipo para expandirse), no debería pasar nada peligroso.
Por otro lado, al deshabilitar la restricción de monomorfismo lo abre a características de rendimiento potencialmente impares, habilitar instancias de sinónimos de tipo lo abre a errores de clase de tipo potencialmente impares. Así que habilitemos -XTypeSynonymInstances
e intentemos escribir una instancia con un sinónimo de tipo:
Prelude> :set -XTypeSynonymInstances
Prelude> instance Num String where (+) = (++)
<interactive>:3:10:
Illegal instance declaration for `Num String''
(All instance types must be of the form (T a1 ... an)
where a1 ... an are *distinct type variables*,
and each type variable appears at most once in the instance head.
Use -XFlexibleInstances if you want to disable this.)
In the instance declaration for `Num String''
String
parece un tipo antiguo simple, por lo que esto puede ser sorprendente al principio; pero en realidad es [Char]
, por lo que esta instancia no es válida según las reglas estrictas de Haskell 2010. Si relajamos esas reglas al activar -XFlexibleInstances
(que, por cierto, implica -XTypeSynonymInstances
), este ejemplo funciona ahora:
Prelude> :set -XFlexibleInstances
Prelude> instance Num String where (+) = (++)
... errors about undefined methods ...
Prelude> "a" + "b"
"ab"
Pero las cosas se ponen feas rápido
Prelude> instance Eq String where
Prelude> "a" == "b"
<interactive>:8:5:
Overlapping instances for Eq [Char]
arising from a use of `==''
Matching instances:
instance Eq a => Eq [a] -- Defined in `GHC.Classes''
instance Eq String -- Defined at <interactive>:7:10
In the expression: "a" == "b"
In an equation for `it'': it = "a" == "b"
Nuevamente, aunque String
parece un tipo distinto, ya tenemos una instancia para [a]
, y esto se superpone con ella. (Y, de hecho, esto es probablemente parte de la razón por la cual -XFlexibleInstances
no está -XFlexibleInstances
por defecto). Y activar -XOverlappingInstances
es una idea mucho más mala que activar -XFlexibleInstances
.
TypeSynonymInstances
es perfectamente seguro. Dado que todo lo que sea potencialmente sofisticado, como los sinónimos de tipo parcialmente aplicados, no está permitido, tiene exactamente el mismo efecto que escribir el lado derecho del sinónimo de tipo en el encabezado de la instancia, es decir
type Foo a = ([a], [a])
instance Bar (Foo a)
es lo mismo que
instance Bar ([a], [a])
Sin embargo, tenga en cuenta que ambas instancias requieren FlexibleInstances
ya que contienen constructores de tipo anidados así como variables de tipo repetido. En general, este será el caso a menudo después de expandir los sinónimos de tipo.
Creo que puede ser la razón por la que son rechazados por defecto.
Sin embargo, FlexibleInstances
es también una extensión perfectamente segura. Lo peor que puede hacer es causar un error en tiempo de compilación si intenta definir instancias superpuestas, como
instance Xyzzy String
instance Xyzzy [a]
En cuanto a por qué FlexibleInstances
no está disponible por defecto, solo puedo suponer que es para simplificar el lenguaje. Las reglas estándar para los jefes de instancia aseguran que la única forma en que las definiciones de instancia pueden superponerse es si los constructores de tipo en los encabezados de instancia son idénticos, mientras que con FlexibleInstances
verificación de solapamientos es un poco más difícil.