Esta respuesta responde a los problemas planteados por illissius, punto por punto:
- Es feo de usar. $ (fooBar '' Asdf) simplemente no se ve bien. Superficial, claro, pero contribuye.
Estoy de acuerdo. Siento que $ () se eligió para que pareciera que era parte del lenguaje, utilizando la paleta de símbolos familiar de Haskell. Sin embargo, eso es exactamente lo que usted / no / quiere en los símbolos utilizados para su empalme de macro. Definitivamente se mezclan demasiado, y este aspecto cosmético es bastante importante. Me gusta la apariencia de {{}} para los empalmes, porque son bastante distintos visualmente.
- Es aún más feo escribir. Las citas a veces funcionan, pero muchas veces tienes que hacer un injerto y plomería AST manual. La [API] [1] es grande y difícil de manejar, siempre hay muchos casos que no le importan pero que aún necesita despachar, y los casos que le interesan tienden a estar presentes en múltiples formas similares pero no idénticas (datos vs. newtype, estilo de registro vs. constructores normales, etc.). Es aburrido y repetitivo escribir y lo suficientemente complicado como para no ser mecánico. La [propuesta de reforma] [2] aborda algo de esto (haciendo que las citas sean más ampliamente aplicables).
Sin embargo, también estoy de acuerdo con esto, como lo observan algunos de los comentarios en "Nuevas direcciones para TH", la falta de una buena cita de AST lista para usar no es un defecto crítico. En este paquete WIP, busco abordar estos problemas en forma de biblioteca: https://github.com/mgsloan/quasi-extras . Hasta ahora, permito empalmar en algunos lugares más de lo habitual y puedo hacer coincidir patrones en AST.
- La restricción de escenario es el infierno. No poder empalmar las funciones definidas en el mismo módulo es la parte más pequeña del mismo: la otra consecuencia es que si tiene un empalme de nivel superior, todo lo que esté después en el módulo estará fuera del alcance de cualquier cosa anterior. Otros lenguajes con esta propiedad (C, C ++) lo hacen viable al permitirle reenviar declaraciones, pero Haskell no. Si necesita referencias cíclicas entre declaraciones empalmadas o sus dependencias y dependientes, generalmente está jodido.
Me he encontrado con el problema de que las definiciones cíclicas de TH son imposibles antes ... Es bastante molesto. Hay una solución, pero es fea: envolver las cosas involucradas en la dependencia cíclica en una expresión TH que combine todas las declaraciones generadas. Uno de estos generadores de declaraciones podría ser un cuasi-cita que acepta el código Haskell.
- No tiene principios. Lo que quiero decir con esto es que la mayoría de las veces cuando expresas una abstracción, hay algún tipo de principio o concepto detrás de esa abstracción. Para muchas abstracciones, el principio detrás de ellas se puede expresar en sus tipos. Cuando define una clase de tipo, a menudo puede formular leyes que las instancias deben obedecer y los clientes pueden asumir. Si utiliza la [nueva función genérica] de GHC [3] para abstraer la forma de una declaración de instancia sobre cualquier tipo de datos (dentro de los límites), puede decir "para tipos de suma, funciona así, para tipos de productos, funciona así ". Pero Template Haskell es simplemente macros tontos. No es la abstracción a nivel de ideas, sino la abstracción a nivel de AST, que es mejor, pero solo modestamente, que la abstracción a nivel de texto sin formato.
Solo no tiene principios si haces cosas sin principios con él. La única diferencia es que con los mecanismos de abstracción implementados por el compilador, tiene más confianza en que la abstracción no tiene fugas. ¡Quizás democratizar el diseño del lenguaje suena un poco aterrador! Los creadores de las bibliotecas TH necesitan documentar bien y definir claramente el significado y los resultados de las herramientas que proporcionan. Un buen ejemplo de TH basado en principios es el paquete de derivación : http://hackage.haskell.org/package/derive : utiliza un DSL tal que el ejemplo de muchas de las derivaciones / especifica / la derivación real.
- Te vincula a GHC. En teoría, otro compilador podría implementarlo, pero en la práctica dudo que esto suceda alguna vez. (Esto está en contraste con varias extensiones de sistema de tipo que, aunque solo podrían ser implementadas por GHC en este momento, fácilmente podría imaginar ser adoptado por otros compiladores en el futuro y eventualmente estandarizado).
Ese es un punto bastante bueno: la API TH es bastante grande y torpe. Volver a implementarlo parece que podría ser difícil. Sin embargo, en realidad solo hay algunas maneras de dividir el problema de representar AST de Haskell. Me imagino que copiar los TH ADT y escribir un convertidor en la representación interna de AST te ayudaría mucho. Esto sería equivalente al esfuerzo (no insignificante) de crear haskell-src-meta. También se podría volver a implementar simplemente imprimiendo bonito el TH AST y utilizando el analizador interno del compilador.
Si bien podría estar equivocado, no veo a TH como una extensión de compilador tan complicada, desde una perspectiva de implementación. Este es en realidad uno de los beneficios de "mantenerlo simple" y no tener la capa fundamental como un sistema de plantillas teóricamente atractivo y verificable estáticamente.
- La API no es estable. Cuando se agregan nuevas funciones de lenguaje a GHC y el paquete template-haskell se actualiza para admitirlas, esto a menudo implica cambios incompatibles con versiones anteriores de los tipos de datos TH. Si desea que su código TH sea compatible con más de una versión de GHC, debe tener mucho cuidado y posiblemente usarlo
CPP
.
Este también es un buen punto, pero algo dramático. Si bien ha habido adiciones de API últimamente, no han sido ampliamente inductoras de roturas. Además, creo que con la cita superior de AST que mencioné anteriormente, la API que realmente necesita ser utilizada puede reducirse sustancialmente. Si ninguna construcción / coincidencia necesita funciones distintas, y en su lugar se expresan como literales, entonces la mayor parte de la API desaparece. Además, el código que escriba se transferirá más fácilmente a las representaciones de AST para idiomas similares a Haskell.
En resumen, creo que TH es una herramienta poderosa, semi-descuidada. Menos odio podría conducir a un ecosistema de bibliotecas más animado, fomentando la implementación de más prototipos de características del lenguaje. Se ha observado que TH es una herramienta dominada, que puede permitirte / hacer / casi cualquier cosa. ¡Anarquía! Bueno, es mi opinión que este poder puede permitirle superar la mayoría de sus limitaciones y construir sistemas capaces de enfoques de metaprogramación bastante basados en principios. Vale la pena usar hacks feos para simular la implementación "adecuada", ya que de esta manera el diseño de la implementación "adecuada" se irá aclarando gradualmente.
En mi versión ideal personal de nirvana, gran parte del lenguaje se movería del compilador a bibliotecas de esta variedad. El hecho de que las características se implementen como bibliotecas no influye mucho en su capacidad de abstraer fielmente.
¿Cuál es la respuesta típica de Haskell al código repetitivo? Abstracción. ¿Cuáles son nuestras abstracciones favoritas? Funciones y clases de tipos!
Las clases de tipos nos permiten definir un conjunto de métodos, que luego se pueden utilizar en todo tipo de funciones genéricas en esa clase. Sin embargo, aparte de esto, la única forma en que las clases ayudan a evitar repeticiones es ofreciendo "definiciones predeterminadas". ¡Ahora aquí hay un ejemplo de una característica sin principios!
Los conjuntos de enlace mínimos no son declarables / compilador comprobable. Esto podría conducir a definiciones inadvertidas que rinden fondo debido a la recursividad mutua.
A pesar de la gran comodidad y potencia que esto produciría, no puede especificar valores predeterminados de superclase, debido a instancias huérfanas http://lukepalmer.wordpress.com/2009/01/25/a-world-without-orphans/ Esto nos permitiría arreglar el jerarquía numérica con gracia!
Buscar las capacidades de TH para los valores predeterminados de los métodos condujo a http://www.haskell.org/haskellwiki/GHC.Generics . Si bien esto es algo genial, mi única experiencia en la depuración de código usando estos genéricos fue casi imposible, debido al tamaño del tipo inducido y ADT tan complicado como un AST. https://github.com/mgsloan/th-extra/commit/d7784d95d396eb3abdb409a24360beb03731c88c
En otras palabras, esto fue después de las características proporcionadas por TH, pero tuvo que levantar un dominio completo del lenguaje, el lenguaje de construcción, en una representación de sistema de tipos. Si bien puedo ver que funciona bien para su problema común, para los complejos, parece propenso a producir una pila de símbolos mucho más aterrador que la piratería informática TH.
TH le proporciona un cálculo en tiempo de compilación a nivel de valor del código de salida, mientras que los genéricos lo obligan a levantar la parte de coincidencia / recursión de patrones del código en el sistema de tipos. Si bien esto restringe al usuario de algunas maneras bastante útiles, no creo que la complejidad valga la pena.
Creo que el rechazo de TH y la metaprogramación de tipo lisp condujo a la preferencia hacia cosas como los valores predeterminados del método en lugar de declaraciones de instancias más flexibles y de macroexpansión. La disciplina de evitar cosas que podrían conducir a resultados imprevistos es sabia, sin embargo, no debemos ignorar que el sistema de tipos capaz de Haskell permite una metaprogramación más confiable que en muchos otros entornos (verificando el código generado).