250 votos

¿Qué tiene de malo Plantilla Haskell?

Parece que la Plantilla de Haskell es a menudo visto por el Haskell de la comunidad como un desafortunado conveniencia. Es difícil poner en palabras exactamente lo que yo he observado a este respecto, pero tenga en cuenta estos pocos ejemplos

He visto varios posts en el blog donde la gente hace cosas bastante limpio, con Plantilla de Haskell, permitiendo la más guapa de la sintaxis que simplemente no sería posible en regular Haskell, así como de grandes repetitivo de reducción. ¿Por qué es que la Plantilla de Haskell es menospreciado en este camino? Lo que lo hace indeseable? ¿En qué circunstancias deberían Plantilla de Haskell ser evitado, y por qué?

171voto

dflemstr Puntos 18999

Una de las razones para evitar la Plantilla de Haskell es que, como un todo, no es seguro, en todo, lo que va en contra de mucho de "el espíritu de Haskell." Aquí están algunos ejemplos de esto:

  • Usted no tiene control sobre qué tipo de Haskell AST un pedazo de TH código generará, más allá de dónde va a aparecer; usted puede tener un valor de tipo Exp, pero no sé si es una expresión que representa un [Char] o (a -> (forall b . b -> c)) o lo que sea. TH sería más fiable si se podría expresar que una función sólo puede generar expresiones de un cierto tipo, o sólo las declaraciones de función, o sólo de datos-constructor de coincidencia de patrones, etc.
  • Usted puede generar expresiones que no se compila. Se genera una expresión que hace referencia a una variable libre foo que no existe? A pesar de la suerte, sólo podrá ver que cuando la realidad utilizando su generador de código, y sólo en virtud de las circunstancias que condicionan la generación de código en particular. Es muy difícil para la unidad de prueba, también.

TH es también abiertamente peligroso:

  • El código que se ejecuta en tiempo de compilación puede hacer arbitrario IO, incluyendo el lanzamiento de misiles o robo de su tarjeta de crédito. No quiero tener que mirar a través de cada cábala paquete que usted siempre descargar en búsqueda de la hazaña.
  • TH puede acceder módulo "privado" de las funciones y definiciones, completamente romper la encapsulación en algunos casos.

A continuación, hay algunos problemas que hacen TH funciones menos divertido de usar como una biblioteca desarrollador:

  • TH código no siempre se puede componer. Supongamos que alguien hace un generador de lentes, y más a menudo que no, que el generador estará estructurado de tal manera que sólo puede ser llamado directamente por el "usuario final", y no por otros TH código, por ejemplo, teniendo una lista de los constructores de tipos para generar lentes para el parámetro. Es difícil generar esa lista en el código, mientras que el usuario sólo tiene que escribir generateLenses [''Foo, ''Bar].
  • Los desarrolladores de ni siquiera saber que el código puede ser compuesto. ¿Sabía usted que usted puede escribir forM_ [''Foo, ''Bar] generateLens? Q es sólo una monada, así que usted puede utilizar todas las funciones usuales en él. Algunas personas no saben esto, y debido a eso, ellos crear múltiples versiones sobrecargadas de esencialmente las mismas funciones con la misma funcionalidad, y estas funciones se llevan a un cierto sobredimensionamiento de efecto. Además, la mayoría de la gente escribe sus generadores en el Q mónada, incluso cuando no tienen, que es como escribir bla :: IO Int; bla = return 3; usted está dando una función más de "medio ambiente" de lo que necesita, y los clientes de la función están obligados a proporcionar ese ambiente como un efecto de eso.

Por último, hay algunas cosas que hacen TH funciones menos divertido de uso como usuario final:

  • La opacidad. Cuando TH en función del tipo Q Dec, que puede generar absolutamente nada, en el nivel superior de un módulo, y usted no tiene absolutamente ningún control sobre lo que va a ser generado.
  • Monolitismo. Usted no puede controlar la cantidad de TH en función genera a menos que el desarrollador lo permite; si encuentra una función que genera una interfaz de base de datos y una serialización JSON interfaz, no puedes decir "No, yo sólo quiero la interfaz de base de datos, gracias; voy a rodar mi propia interfaz JSON"
  • Tiempo de ejecución. TH código se toma un tiempo relativamente largo para que se ejecute. El código es interpretado de nuevo cada vez que un archivo es compilado, y, a menudo, una tonelada de paquetes requeridos por la ejecución de TH código, que tienen que ser cargados. Esto ralentiza el tiempo de compilación considerablemente.

51voto

glaebhoerl Puntos 3182

Esto es solamente mi opinión.

  • Es feo a utilizar. $(fooBar ''Asdf) simplemente no se ven bien. Superficial, seguro, pero contribuye.

  • Es aún más feo para escribir. Citando a veces funciona, pero a un montón de tiempo que usted tiene que hacer manual de AST injerto y de plomería. El API es grande y difícil de manejar, siempre hay un montón de casos que no les importa, pero sigue siendo necesario el envío, y los casos de atención sobre tienden a estar presente en varios similar pero no idéntica forma (datos vs. newtype, registro de estilo vs. normal constructores, etc.). Es aburrido y repetitivo a escribir y lo suficientemente complicado como para no ser mecánico. La propuesta de reforma de las direcciones de algunos de este (haciendo comillas más ampliamente aplicable).

  • La etapa de restricción es el infierno. No ser capaz de empalme de funciones definidas en el mismo módulo es la parte más pequeña de ti: la otra consecuencia es que si usted tiene un nivel superior de empalme, todo después de que en el módulo estará fuera de alcance para cualquier cosa antes que a él. Otros idiomas con esta propiedad (C, C++) haga viable por permitir que usted envíe declarar cosas, pero Haskell no. Si usted necesita referencias cíclicas entre empalmado declaraciones o sus dependencias y dependientes, por lo general, acaba de follar.

  • Es indisciplinado. Lo que quiero decir con esto es que la mayoría de las veces cuando se expresa una abstracción, hay algún tipo de principio o concepto detrás de esa abstracción. Para muchos de las abstracciones, el principio detrás de ellos puede ser expresada en sus tipos. Para las clases de tipo, frecuencia, usted puede formular las leyes que las instancias deberán obedecer y los clientes pueden asumir. Si utiliza GHC del nuevo genéricos característica de abstraer la forma de una instancia de la declaración sobre cualquier tipo de datos (dentro de los límites), se llega a decir que "a la suma de los tipos, esto funciona así, por tipos de producto, funciona así". Plantilla de Haskell, por otro lado, se acaba de macros. No es la abstracción a nivel de ideas, sino de la abstracción en el nivel de Ast, que es mejor, pero sólo de forma moderada, de la abstracción a nivel de texto sin formato.*

  • Vincula a GHC. En la teoría de otro compilador podría poner en práctica, pero en la práctica dudo que esto nunca sucederá. (Esto es en contraste a diferentes tipo de sistema de extensiones, que, a pesar de que sólo puede ser implementado por el GHC en el momento, yo podría fácilmente imaginar siendo adoptadas por otros compiladores de la carretera y finalmente estandarizado.)

  • La API no es estable. Cuando las nuevas características del lenguaje se agregan a GHC y la plantilla-haskell paquete se actualiza para ellos, esto implica a menudo hacia atrás-cambios incompatibles para el TH tipos de datos. Si quieres que tu TH código para que sea compatible con más de una versión de GHC tiene que ser muy cuidadoso y, posiblemente, el uso CPP.

  • Hay un principio general que debe utilizar la herramienta adecuada para el trabajo y el más pequeño será más que suficiente, y en la analogía de la Plantilla de Haskell es algo como esto. Si hay una manera de hacerlo que no es de la Plantilla de Haskell, es generalmente preferible.

La ventaja de la Plantilla de Haskell es que usted puede hacer cosas con él que no se podía hacer ninguna otra forma, y es un grande. La mayoría del tiempo las cosas TH se utiliza para lo contrario, podría realizarse sólo si se aplican directamente como de las características del compilador. TH es muy beneficioso tener tanto porque le permite hacer estas cosas, y porque permite prototipo potencial compilador extensiones de una forma mucho más ligero y reutilizable (ver los distintos lente de paquetes, por ejemplo).

Para un resumen de por qué creo que hay sentimientos negativos hacia Plantilla de Haskell: resuelve un montón de problemas, pero para cualquier problema que se soluciona, se siente como debería haber un mejor, más elegante, disciplinada solución se adapta mejor a la solución de ese problema, que no resuelve el problema mediante la generación automática de la repetitivo, pero eliminando la necesidad de tener la repetitivo.

* A pesar de que a menudo siento que CPP tiene un poder mejor relación peso-para aquellos problemas que puede resolver.

EDITAR 23-04-14: Lo que yo era, con frecuencia, tratando de llegar a la de arriba, y sólo recientemente han conseguido exactamente, es que no hay una distinción importante entre la abstracción y la eliminación de duplicados. Adecuado de la abstracción a menudo resulta en la deduplicación como un efecto secundario, y la duplicación es a menudo un signo revelador de la inadecuación de la abstracción, pero no es por eso es valioso. Adecuado de la abstracción es lo que hace el código correcto, comprensible y fácil de mantener. La deduplicación sólo hace que sea más corto. Plantilla de Haskell, como las macros, en general, es una herramienta de eliminación de duplicados.

30voto

mgsloan Puntos 601

Me gustaría abordar algunos de los puntos dflemstr trae.

No me parece que el hecho de que no se puede-comprobación de tipo TH ser que preocupante. ¿Por qué? Porque incluso si no es un error, será la hora de compilar. No estoy seguro de si esto refuerza mi argumento, pero esto es similar en espíritu a los errores que usted recibe cuando el uso de plantillas en C++. Yo creo que estos errores son más comprensible que la de C++errores, aunque, como usted obtendrá una bonita versión impresa del código generado.

Si una expresión de TH / cuasi-quoter hace algo que es tan avanzado que difícil esquinas se puede ocultar, entonces tal vez mal aconsejado?

Romper esta regla un poco con la cuasi-quoters he estado trabajando últimamente (usando haskell-src-exts / meta) - https://github.com/mgsloan/quasi-extras/tree/master/examples . Sé que esto presenta algunos errores tales como no ser capaz de empalme en la generalización de la lista de comprensión. Sin embargo, creo que hay una buena probabilidad de que algunas de las ideas en http://hackage.haskell.org/trac/ghc/blog/template%20haskell%20proposal va a terminar en el compilador. Hasta entonces, las bibliotecas para el análisis de Haskell para TH árboles son una casi perfecta aproximación.

En relación con la elaboración de velocidad / dependencias, podemos utilizar el "cero" paquete de poner en línea el código generado. Esto es al menos agradable para los usuarios de una biblioteca, pero no lo podemos hacer mucho mejor para el caso de la edición de la biblioteca. Puede TH dependencias de la hinchazón genera archivos binarios? Pensé que a la izquierda de todo lo que no se hace referencia por el código compilado.

La puesta en escena de la restricción / división de compilación pasos de la Haskell módulo de chupar.

RE Opacidad: es la misma para cualquier función de la biblioteca de la llamada. Usted no tiene ningún control sobre qué Datos.Lista de.groupBy va a hacer. Usted acaba de tener un razonable "garantía" / convención de que los números de versión de decirle algo acerca de la compatibilidad. Es un poco de una cuestión diferente de cambio cuando.

Esto es donde el uso del cero paga - ya estás control de versiones de los archivos generados - de modo que siempre sabrá cuando la forma de generar el código ha cambiado. Buscar en el diffs podría ser un poco difícil, sin embargo, para grandes cantidades de código generado, por lo que es un lugar donde un mejor desarrollador de interfaz sería muy útil.

RE Monolitismo: Usted puede, sin duda, el post-proceso de los resultados de una expresión de TH, utilizando su propio tiempo de compilación de código. No sería mucho código para filtrar en la parte superior a nivel de declaración de tipo / nombre. Heck, usted se pueda imaginar, escribir una función que hace esto de forma genérica. Para modificar / de-monolithisizing quasiquoters, puede coincidencia de patrón en "QuasiQuoter" y sacar a cabo las transformaciones que se utiliza, o hacer uno nuevo en términos de la edad.

15voto

mgsloan Puntos 601

Esta respuesta es en respuesta a las cuestiones planteadas por illissius, punto por punto:

  • Es feo a utilizar. $(fooBar "Asdf) simplemente no se ven bien. Superficial, seguro, pero contribuye.

Estoy de acuerdo. Me siento como $( ) fue elegido como fue parte de la lengua - mediante el conocido símbolo de la paleta de Haskell. Sin embargo, eso es exactamente lo que no quiero en los símbolos utilizados para la macro de empalme. Definitivamente mezcla demasiado, y este aspecto cosmético es muy importante. Me gusta el look de {{ }} para empalmes, porque son bastante distinto.

  • Es aún más feo para escribir. Citando a veces funciona, pero a un montón de tiempo que usted tiene que hacer manual de AST injerto y de plomería. El [API][1] es grande y difícil de manejar, siempre hay un montón de casos que no les importa, pero sigue siendo necesario el envío, y los casos de atención sobre tienden a estar presente en varios similar pero no idéntica forma (datos vs. newtype, registro de estilo vs. normal constructores, etc.). Es aburrido y repetitivo a escribir y lo suficientemente complicado como para no ser mecánico. [Propuesta de reforma][2] las direcciones de algunos de este (haciendo comillas más ampliamente aplicable).

También estoy de acuerdo con esto, sin embargo, como algunos de los comentarios en "Nuevas Orientaciones de la TH" observar, la falta de una buena out-of-the-box AST citando no es un defecto crítico. En este WIP paquete, me buscan para abordar estos problemas en la biblioteca forma: https://github.com/mgsloan/quasi-extras . Así que ahora me permiten el empalme en unos lugares más que de costumbre, y puede coincidencia de patrón en ASTs.

  • La etapa de restricción es el infierno. No ser capaz de empalme de funciones definidas en el mismo módulo es la parte más pequeña de ti: la otra consecuencia es que si usted tiene un nivel superior de empalme, todo después de que en el módulo estará fuera de alcance para cualquier cosa antes que a él. Otros idiomas con esta propiedad (C, C++) haga viable por permitir que usted envíe declarar cosas, pero Haskell no. Si usted necesita referencias cíclicas entre empalmado declaraciones o sus dependencias y dependientes, por lo general, acaba de follar.

Me he topado con el problema de la TH definiciones ser imposible antes... Es bastante molesto. Hay una solución, pero es feo de envolver las cosas que intervienen en el ciclo de dependencia en una expresión de TH que combina todos los generados declaraciones. Una de estas declaraciones generadores podría ser sólo una cuasi-quoter que acepta Haskell código.

  • Es inescrupuloso. Lo que quiero decir con esto es que la mayoría de las veces cuando se expresa una abstracción, hay algún tipo de principio o concepto detrás de esa abstracción. Para muchos de las abstracciones, el principio detrás de ellos puede ser expresada en sus tipos. Cuando se define un tipo de la clase, usted puede a menudo formular leyes que las instancias deberán obedecer y los clientes pueden asumir. Si utiliza GHC [nueva genéricos característica][3] para abstraer la forma de una instancia de la declaración sobre cualquier tipo de datos (dentro de los límites), se llega a decir que "a la suma de los tipos, esto funciona así, por tipos de producto, funciona así". Pero la Plantilla de Haskell es simplemente tonto macros. No es la abstracción a nivel de ideas, sino de la abstracción en el nivel de Ast, que es mejor, pero sólo de forma moderada, de la abstracción a nivel de texto sin formato.

Es sólo inmoral si no inescrupuloso cosas con él. La única diferencia es que con el compilador implementado mecanismos de abstracción, tiene más confianza en que la abstracción no es permeable. Tal vez la democratización del diseño del lenguaje suena un poco de miedo! Los creadores de TH bibliotecas necesidad de documentar bien y definir claramente el significado y los resultados de las herramientas que proporcionan. Un buen ejemplo de principios TH es la deriva del paquete: http://hackage.haskell.org/package/derive - utiliza una conexión DSL tal que el ejemplo de muchas de las derivaciones /especifica/ el real de la derivación.

  • Vincula a GHC. En la teoría de otro compilador podría poner en práctica, pero en la práctica dudo que esto nunca sucederá. (Esto es en contraste a diferentes tipo de sistema de extensiones, que, a pesar de que sólo puede ser implementado por el GHC en el momento, yo podría fácilmente imaginar siendo adoptadas por otros compiladores de la carretera y finalmente estandarizado.)

Es un buen punto - TH el API es bastante grande y tosco. Implementar de nuevo parece que podría ser difícil. Sin embargo, sólo hay un par de maneras de cortar el problema de la representación de Haskell ASTs. Me imagino que la copia de la TH Tdas, y la escritura de un convertidor interno AST representación llevaría a que una buena parte del camino. Esto sería equivalente a el (no despreciable) esfuerzo de creación de haskell-src-meta. También podría ser simplemente re-implementado por la buena impresión de la TH AST y utilizando el compilador interno del analizador.

Aunque podría estar equivocado, no veo TH como el complicado de una extensión de compilador, a partir de la implementación de la perspectiva. Este es en realidad uno de los beneficios de "mantenerlo simple" y no tener la capa básica de ser algún teóricamente atractiva, estáticamente verificable sistema de plantillas.

  • La API no es estable. Cuando las nuevas características del lenguaje se agregan a GHC y la plantilla-haskell paquete se actualiza para ellos, esto implica a menudo hacia atrás-cambios incompatibles para el TH tipos de datos. Si quieres que tu TH código para que sea compatible con más de una versión de GHC tiene que ser muy cuidadoso y, posiblemente, el uso CPP.

Este es también un buen punto, pero algo dramaticized. Aunque no se han API adiciones últimamente, no han sido ampliamente la rotura de la inducción. También, yo creo que con el superior AST citando he mencionado anteriormente, el API que realmente se necesita para ser usado puede ser muy reducido sustancialmente. Si no, la construcción o la coincidencia de necesidades distintas funciones, y en su lugar se expresa como literales, entonces la mayoría de la API desaparece. Por otra parte, el código de escritura sería el puerto más fácilmente a AST representaciones para las lenguas similar a la de Haskell.


En resumen, creo que TH es un poderoso, semi-abandonados de la herramienta. Menos odio podría conducir a un más vivo eco-sistema de bibliotecas, impulsar la implementación de más característica del lenguaje prototipos. Se ha observado que TH es un dominado herramienta, que puede dejar /hacer/ casi nada. La anarquía! Bueno, es mi opinión que este poder puede permitir que usted para superar la mayoría de sus limitaciones, y construir sistemas capaces de principios de meta-criterios de programación. Vale la pena el uso de feo hacks para simular la "correcta" aplicación, de esta manera el diseño de la "correcta" aplicación poco a poco será evidente.

En mi ideal personal versión de nirvana, tanto de la lengua en realidad iba a mover fuera del compilador, en las bibliotecas de estas opciones. El hecho de que las funciones se implementan como las bibliotecas no influyen fuertemente en su capacidad fielmente abstracto.

¿Cuál es el típico Haskell respuesta a el código repetitivo? La abstracción. ¿Cuáles son nuestros favoritos abstracciones? Funciones y typeclasses!

Typeclasses vamos a definir un conjunto de métodos, que pueden ser utilizados en todo tipo de funciones genéricas de la clase. Sin embargo, aparte de esto, la única forma de clases para ayudar a evitar repetitivo es ofrecer "las definiciones de default". Ahora, aquí está un ejemplo de un inescrupuloso característica!

  • Mínima de unión de conjuntos no son declarables / compilador seleccionable. Esto podría conducir a la definición de rendimiento inferior debido a la recursividad.

  • A pesar de la gran comodidad y la potencia de este produciría, usted puede especificar superclase valores predeterminados, debido a los huérfanos instancias http://lukepalmer.wordpress.com/2009/01/25/a-world-without-orphans/ Estos nos permitirá fijar el número de la jerarquía de gracia!

  • Va después del TH-como las capacidades para el método de los valores predeterminados de led para http://www.haskell.org/haskellwiki/ghc.generics . Mientras que esto es cool stuff, mi única experiencia en la depuración de código mediante el uso de estos fármacos genéricos era casi imposible, debido al tamaño de la de tipo inducido y ADT tan complicado como un AST. https://github.com/mgsloan/th-extra/commit/d7784d95d396eb3abdb409a24360beb03731c88c

    En otras palabras, esto pasó después de que las características proporcionadas por TH, pero tuvo que levantar un dominio completo de la lengua, de la construcción del lenguaje, en un tipo de sistema de representación. Mientras puedo ver que funcione bien para su problema común, por complejos, parece propenso a ceder a un montón de símbolos mucho más aterrador que TH trucos.

    TH le da valor a nivel de tiempo de compilación cómputo del código de salida, mientras que los genéricos fuerzas para levantar la coincidencia de patrón / recursividad parte del código en el sistema de tipos. Mientras esto no se limitan al usuario en un par de bastante útil maneras, no creo que la complejidad es la pena.

Creo que el rechazo de la TH y lisp-como metaprogramación llevó a la preferencia hacia cosas como el método de los valores predeterminados en lugar de más flexible, macro-expansión como las declaraciones de los casos. La disciplina de evitar las cosas que podría conducir a resultados imprevistos es sabio, sin embargo, no debemos ignorar que Haskell es capaz tipo de sistema permite más confiable de metaprogramación que en muchos otros entornos (por la comprobación de que el código generado).

8voto

Joachim Breitner Puntos 9238

Una vez pragmático problema con la Plantilla de Haskell es que sólo funciona cuando GHC bytecode de la intérprete está disponible, que no es el caso en todas las arquitecturas. Así que si su programa utiliza la Plantilla de Haskell o se basa en las bibliotecas que usan, no se ejecutan en máquinas con un ARM, MIPS, S390 o PowerPC CPU.

Esto es relevante en la práctica: git-el anexo es una herramienta escrito en Haskell, que tiene sentido para ejecutarse en máquinas de preocuparse de almacenamiento, tales máquinas tienen a menudo no-i386-Cpu. Yo, personalmente, ejecutar git-anexo sobre un NSLU 2 (32 MB de RAM, 266 mhz de la CPU; ¿sabía usted Haskell funciona bien en este tipo de hardware?) Si el uso de la Plantilla de Haskell, esto no es posible.

(La situación acerca de GHC en el BRAZO es la mejora en estos días un montón y creo que 7.4.2 aún funciona, pero el punto sigue en pie).

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X