2012-06-08 6 views
17

Pivoteando algunas preguntas recientes, pensé que pondría el centro de atención en el viejo coco, OverlappingInstances.¿Qué hay de malo en OverlappingInstances?

Hace algunos años podría haber hecho esta pregunta en serio: después de todo, puede proporcionar instancias predeterminadas útiles y otras pueden anularlas con otras más específicas cuando lo necesiten, ¿qué puede ser tan malo al respecto?

A lo largo del camino he absorbido algunas apreciaciones para el punto de vista de que OverlappingInstances realmente no es tan limpio, y es mejor evitarlo; principalmente debido al hecho de que no está bien fundamentada teóricamente, a diferencia de otras grandes extensiones.

Pero pensándolo bien, no estoy seguro de poder explicarle a otra persona lo que realmente es tan malo, si me pidieran.

Lo que estoy buscando son ejemplos específicos de maneras en que el uso de OverlappingInstances puede llevar a que sucedan cosas malas, ya sea subvirtiendo el sistema de tipos u otras invariantes, o simplemente inesperado general o desorden.

Un problema particular que conozco es que rompe la propiedad de que simplemente agregar o quitar una importación de un solo módulo no puede cambiar el significado de su programa, porque con la extensión activada, una nueva superposición de instancia podría agregarse silenciosamente o remoto. Aunque puedo ver por qué es desagradable, no veo por qué es terriblemente espantoso.

Pregunta extra: Siempre y cuando nos refiramos a extensiones útiles pero no teóricamente bien fundamentadas que pueden conducir a malos sucesos, ¿cómo es que GeneralizedNewtypeDeriving no tiene la misma mala reputación? ¿Es porque las posibilidades negativas son más fáciles de localizar? que es más fácil ver lo que podría causar problemas y decir, "no hagas eso"?

:

EDITAR (Nota yo preferiría si la peor parte de la respuesta se centra en OverlappingInstances, no IncoherentInstances que necesita menos explicaciones.): También hay buenas respuestas a una pregunta similar here.

+5

Lo malo de 'GeneralizedNewtypeDeriving' es un error de implementación. No hay nada malo acerca de esta extensión como tal, pero ghc lo permite en casos donde debería ser prohibido. – augustss

+1

Relacionados: http://stackoverflow.com/questions/10830757/is-there-a-list-of-ghc-extensions-that-are-considered-safe –

+2

@augustss, ¿es así de simple? Ver http://hackage.haskell.org/trac/ghc/ticket/5498. Simon dice que no sabe de una prueba sintáctica simple para determinar cuándo debería prohibirse y que requiere nuevos trabajos teóricos y avances en el verificador de tipos para que sea seguro. – glaebhoerl

Respuesta

18

Uno de los principios que el lenguaje haskell intenta cumplir es agregar métodos/clases adicionales o instancias en un módulo dado no deberían causar ningún otro módulo que dependa del módulo dado o no compilar o tener un comportamiento diferente (siempre y cuando como los módulos dependientes usan listas de importación explícitas).

Desafortunadamente, esto se rompe con OverlappingInstances. Por ejemplo:

Módulo A:

{-# LANGUAGE FlexibleInstances, OverlappingInstances, MultiParamTypeClasses, FunctionalDependencies #-} 

module A (Test(..)) where 

class Test a b c | a b -> c where 
    test :: a -> b -> c 

instance Test String a String where 
    test str _ = str 

Módulo B:

module B where 
import A (Test(test)) 

someFunc :: String -> Int -> String 
someFunc = test 

shouldEqualHello = someFunc "hello" 4 

shouldEqualHello es igual a "hola" en el módulo B.

Ahora agregue la siguiente declaración de la instancia en A:

instance Test String Int String where 
    test s i = concat $ replicate i s 

Sería preferible si esto no afectara al módulo B. Funcionó antes de esta adición, y debería funcionar después. Lamentablemente, este no es el caso.

El módulo B aún compila, pero ahora shouldEqualHello ahora es igual a "hellohellohellohello".El comportamiento ha cambiado a pesar de que no había cambiado ningún método que originalmente se usaba.

Lo que es peor es que no hay forma de volver al comportamiento anterior, ya que no puede optar por no importar una instancia desde un módulo. Como se puede imaginar, esto es muy malo para la compatibilidad con versiones anteriores, ya que no puede agregar nuevas instancias a una clase que usa superposiciones, ya que podría cambiar el comportamiento del código que usa el módulo (especialmente cierto si está escribiendo código de biblioteca). Esto es peor que un error de compilación, ya que podría ser muy difícil rastrear el cambio.

El único momento seguro para usar instancias superpuestas en mi opinión es cuando está escribiendo una clase que sabe que nunca necesitará instancias adicionales. Esto puede ocurrir si está haciendo algún tipo de código basado en el tipo complicado.

+0

Ya veo. Ese es básicamente el problema que reconocí en la pregunta, pero has hecho un buen trabajo al explicar por qué es malo. ¿Es este el único problema real con eso? – glaebhoerl

+4

Sí, el "único" problema real es que puede perder completamente la confluencia y la capacidad de razonar sobre su código. Funciona de alguna manera si tiene cuidado de definir todas las instancias que se superponen en un módulo, pero hay formas de abusar de los tipos de restricciones para hacer cosas malas en las que pasa el caso más específico, la instancia menos específica. –

+1

@EdwardKmett ¿Qué sentido tiene usar la palabra "confluencia" aquí? ¿El mismo que para los sistemas de reescritura de términos? – glaebhoerl

Cuestiones relacionadas