2010-06-20 16 views
74

al compilar mi aplicación Haskell con la opción -Wall, GHC se queja de las instancias huérfanas, por ejemplo:instancias huérfanas en Haskell

Publisher.hs:45:9: 
    Warning: orphan instance: instance ToSElem Result 

La clase de tipo ToSElem no es mío, es definida por HStringTemplate.

Ahora sé cómo solucionar esto (muevo la declaración de la instancia al módulo donde se declara el resultado), y sé why GHC would prefer to avoid orphaned instances, pero sigo creyendo que mi camino es mejor. No me importa si el compilador tiene inconvenientes, más bien que yo.

La razón por la que deseo declarar mis instancias ToSElem en el módulo de Publicador es porque es el módulo de Publicador que depende de HStringTemplate, no los otros módulos. Estoy tratando de mantener una separación de preocupaciones y evitar que cada módulo dependa de HStringTemplate.

Pensé que una de las ventajas de las clases de tipos de Haskell, en comparación con las interfaces de Java, es que son abiertas en lugar de cerradas y por lo tanto las instancias no tienen que declararse en el mismo lugar que el tipo de datos . El consejo de GHC parece ser ignorar esto.

Entonces, lo que estoy buscando es alguna confirmación de que mi pensamiento es sólido y que estaría justificado al ignorar/suprimir esta advertencia, o un argumento más convincente en contra de hacer las cosas a mi manera.

+0

La discusión en las respuestas y comentarios ilustra que hay una gran diferencia entre la definición de instancias huérfanas en un * ejecutable *, como lo está haciendo, frente a una * biblioteca * que está expuesta a otros. [Esta pregunta tremendamente popular] (http://stackoverflow.com/q/26770247/) ilustra cuán confusas pueden ser las instancias huérfanas para los usuarios finales de una biblioteca que las define. –

Respuesta

80

Entiendo por qué quiere hacer esto, pero desafortunadamente ser solo una ilusión de que las clases de Haskell parecen estar "abiertas" en la forma en que dices. Muchas personas sienten que la posibilidad de hacer esto es un error en la especificación Haskell, por razones que explicaré a continuación. De todos modos, si realmente no es apropiado para la instancia que necesita declararse en el módulo donde se declara la clase o en el módulo donde se declara el tipo, es probable que sea una señal de que debe estar usando un newtype o algún otro envoltorio alrededor de tu tipo.

Las razones por las cuales las instancias huérfanas deben evitarse son mucho más profundas que la conveniencia del compilador. Este tema es bastante controvertido, como puede ver en otras respuestas. Para equilibrar la discusión, voy a explicar el punto de vista de que nunca se deben escribir instancias huérfanas, que creo que es la opinión mayoritaria entre los Haskellers experimentados. Mi propia opinión está en algún punto intermedio, que explicaré al final.

El problema surge del hecho de que cuando existe más de una declaración de instancia para la misma clase y tipo, no existe un mecanismo en Haskell estándar para especificar cuál usar. Más bien, el programa es rechazado por el compilador.

El efecto más simple de eso es que podría tener un programa perfectamente funcional que de repente dejaría de compilar debido a un cambio que alguien más hace en alguna dependencia lejana de su módulo.

Peor aún, es posible que un programa que funcione inicie bloqueándose en el tiempo de ejecución debido a un cambio lejano. Podría estar utilizando un método que está asumiendo que proviene de una declaración de instancia determinada, y podría ser reemplazado silenciosamente por una instancia diferente que sea lo suficientemente diferente como para causar que su programa comience a fallar inexplicablemente.

Las personas que desean garantías de que estos problemas nunca les pasarán deben seguir la regla de que si alguien, en algún lugar, alguna vez ha declarado una clase determinada para cierto tipo, ninguna otra instancia debe ser declarada nuevamente en cualquier programa escrito por cualquier persona. Por supuesto, existe la solución de utilizar un newtype para declarar una nueva instancia, pero eso siempre es al menos un inconveniente menor, y en ocasiones uno importante. En este sentido, aquellos que escriben intencionalmente instancias huérfanas son bastante descorteses.

Entonces, ¿qué se debe hacer con respecto a este problema? El campo anti-huérfano dice que la advertencia de GHC es un error, debe ser un error que rechace cualquier intento de declarar una instancia huérfana. Mientras tanto, debemos ejercer autodisciplina y evitarlos a toda costa.

Como ha visto, hay quienes no están tan preocupados por esos posibles problemas. De hecho, fomentan el uso de instancias huérfanas como una herramienta para la separación de preocupaciones, como usted sugiere, y dicen que uno debe asegurarse, caso por caso, de que no hay problema. Algunas instancias huérfanas de otras personas me han molestado lo suficiente como para convencerme de que esta actitud es demasiado arrogante.

Creo que la solución correcta sería agregar una extensión al mecanismo de importación de Haskell que controlaría la importación de instancias. Eso no resolvería los problemas por completo, pero ayudaría a proteger nuestros programas contra el daño de las instancias huérfanas que ya existen en el mundo. Y luego, con el tiempo, podría convencerme de que en ciertos casos limitados, quizás una instancia huérfana podría no ser tan mala. (Y esa misma tentación es la razón por la que algunos en el campo antihuérfano se opusieron a mi propuesta.)

Mi conclusión de todo esto es que al menos por el momento, le recomendaría encarecidamente que evite declarar cualquier instancia huérfana, ser considerado con los demás si no es por ninguna otra razón. Use un newtype.

+4

En particular, esto es un problema creciente con el crecimiento en las bibliotecas. Con más de 2200 bibliotecas en Haskell y 10 de miles de módulos individuales, el riesgo de recoger instancias aumenta drásticamente. –

+13

Re: "Creo que la solución correcta sería agregar una extensión al mecanismo de importación de Haskell que controlaría la importación de instancias" En caso de que esta idea interese a alguien, podría valer la pena mirar el lenguaje Scala para ver un ejemplo; tiene características muy similares a esta para controlar el alcance de 'implicits', que se pueden usar de forma similar a las instancias de clase de tipo. – Matt

+3

Mi software es una aplicación en lugar de una biblioteca, por lo que la posibilidad de causar problemas a otros desarrolladores es prácticamente nula. Podría considerar el módulo Editor la aplicación y el resto de los módulos como una biblioteca, pero si tuviera que distribuir la biblioteca, sería sin el publicador y, por lo tanto, las instancias huérfanas. Pero si moviera las instancias a los otros módulos, la biblioteca se enviaría con una dependencia innecesaria de HStringTemplate. Entonces, en este caso, creo que los huérfanos están bien, pero haré caso a su consejo si encuentro el mismo problema en un contexto diferente. –

38

¡Adelante, suprima esta advertencia!

Estás en buena compañía. Conal lo hace en "TypeCompose". "chp-mtl" y "chp-transformers" lo hacen, "control-monad-exception-mtl" y "control-monad-exception-monadsfd" lo hacen, etc.

por cierto, probablemente ya lo sepan, pero para aquellos que no tropiece y su pregunta en una búsqueda:

{-# OPTIONS_GHC -fno-warn-orphans #-} 

Editar:

Reconozco los problemas que Yitz mencionó en su respuesta como problemas reales. Sin embargo, no veo el uso de instancias huérfanas como un problema, y ​​trato de elegir el "menor de todos los males", que es prudente usar instancias huérfanas.

Solo utilicé un signo de exclamación en mi breve respuesta porque su pregunta muestra que ya conoce bien los problemas.De lo contrario, habría sido menos entusiasta :)

Un poco de un desvío, pero lo que creo que es la solución perfecta en un mundo perfecto y sin compromiso:

Creo que los problemas Yitz (no menciona Saber qué instancia está recogido) podría resolverse en un sistema de programación "integral" donde:

  • no está editando archivos mero texto primitivamente, pero está bien atendido por el medio ambiente (por finalización de código ejemplo solamente sugieren cosas de relevancia tipos, etc.)
  • El lenguaje de "nivel inferior" no tiene soporte especial para clases de tipo, y en su lugar las tablas de funciones se pasan explícitamente
  • Pero el entorno de programación de "nivel superior" muestra el código de forma similar a cómo se presenta Haskell ahora (por lo general, no verá las tablas de funciones transmitidas) y selecciona las clases de tipos explícitas cuando son obvias (por ejemplo, todos los casos de Functor tienen una sola opción) y cuando hay varios ejemplos (lista de comprimir Aplicativo o lista -monad Applicative, First/Last/lift tal vez Monoid) le permite elegir qué instancia utilizar.
  • En cualquier caso, incluso cuando se tomó la instancia de forma automática, el medio ambiente fácilmente le permite ver qué instancia se utilizó, con una interfaz fácil (una interfaz de hipervínculo o vuelo estacionario o algo así)

Volver del mundo de fantasía (o con suerte el futuro), en este momento: recomiendo tratar de evitar instancias huérfanas mientras las sigo usando cuando realmente "necesita"

+4

Sí, pero podría decirse que cada una de esas ocurrencias es un error de algún orden. Las malas instancias en control-monad-exception-mtl y monads-fd for O me vienen a la mente. Sería menos perturbador que cada uno de esos módulos se viera obligado a definir sus propios tipos o a suministrar envoltorios de tipo nuevo. Casi cada instancia huérfana es un dolor de cabeza a la espera de ocurrir, y si nada más requiere su vigilancia constante para garantizar que se importe o no según corresponda. –

+2

Gracias. Creo que los usaré en esta situación particular, pero gracias a Yitz ahora tengo una mejor apreciación de los problemas que pueden causar. –

16

En este caso, creo que el uso de instancias huérfanas está bien. La regla general para mí es que puede definir una instancia si es "propietario" de la clase de letra o si "posee" el tipo de datos (o algún componente del mismo), es decir, una instancia para Maybe MyData también está bien, al menos algunas veces). Dentro de esas restricciones, donde decide poner la instancia es su propio negocio.

Hay una excepción más: si no posee la clase de tipo o el tipo de datos, pero está produciendo un archivo binario y no una biblioteca, está bien también.

31

Las instancias huérfanas son una molestia, pero en mi opinión a veces son necesarias. A menudo combino bibliotecas donde un tipo proviene de una biblioteca y una clase proviene de otra biblioteca. Por supuesto, no se puede esperar que los autores de estas bibliotecas proporcionen instancias para cada combinación concebible de tipos y clases. Entonces tengo que proporcionarlos, y entonces son huérfanos.

La idea de que debe ajustar el tipo en un nuevo tipo cuando necesita proporcionar una instancia es una idea con mérito teórico, pero es demasiado tedioso en muchas circunstancias; es el tipo de idea presentada por personas que no escriben el código Haskell para ganarse la vida. :)

Así que adelante y proporcione instancias huérfanas. Ellos son inofensivos.
Si puede bloquear ghc con instancias huérfanas, entonces eso es un error y se debe informar como tal. (El error que ghc tuvo/tiene sobre no detectar instancias múltiples no es tan difícil de arreglar.)

Pero tenga en cuenta que en algún momento en el futuro alguien más podría agregar alguna instancia como la que ya tiene, y es posible que obtenga un error (tiempo de compilación).

+2

Un buen ejemplo es '(Ord k, arbitrario k, arbitrario v) ⇒ arbitrario (Map k v)' cuando se utiliza QuickCheck. –

3

En este sentido, entiendo las bibliotecas WRT de posición del campo de instancia antihuérfano, pero para los objetivos ejecutables ¿no deberían las instancias huérfanas estar bien?

+3

En términos de ser descortés con los demás, tienes razón. Pero te estás abriendo a posibles problemas futuros si la misma instancia se define alguna vez en el futuro en algún lugar de tu cadena de dependencia. Entonces, en este caso, depende de usted decidir si vale la pena el riesgo. – Yitz

+5

En casi todos los casos de implementación de una instancia huérfana en un archivo ejecutable, es para llenar un espacio que * desea * ya estaba definido para usted. Entonces, si la instancia aparece en sentido ascendente, el error de compilación resultante es solo una señal útil para decirle que puede eliminar su declaración de la instancia. – Ben

5

(Sé que voy tarde a la fiesta, pero esto puede todavía ser útil a los demás)

Se podría mantener las instancias huérfanas en su propio módulo, entonces si alguien importa que el módulo está específicamente porque necesitan ellos y pueden evitar importarlos si causan problemas.

Cuestiones relacionadas