Cette réponse répond aux questions soulevées par illissius, point par point:
- C'est moche à utiliser. $ (fooBar '' Asdf) n'a tout simplement pas l'air sympa. Superficiel, certes, mais ça y contribue.
Je suis d'accord. J'ai l'impression que $ () a été choisi pour ressembler à une partie du langage - en utilisant la palette de symboles familière de Haskell. Cependant, c'est exactement ce que vous / ne voulez / pas dans les symboles utilisés pour votre épissage de macro. Ils se fondent définitivement trop et cet aspect cosmétique est assez important. J'aime l'apparence de {{}} pour les épissures, car elles sont assez distinctes visuellement.
- C'est encore plus laid d'écrire. Citer fonctionne parfois, mais la plupart du temps, vous devez effectuer une greffe et une plomberie AST manuelles. L '[API] [1] est grande et peu maniable, il y a toujours beaucoup de cas dont vous ne vous souciez pas, mais que vous devez quand même envoyer, et les cas dont vous vous souciez ont tendance à être présents sous plusieurs formes similaires mais pas identiques (données vs newtype, record-style vs normal constructors, etc.). C'est ennuyeux et répétitif d'écrire et assez compliqué pour ne pas être mécanique. La [proposition de réforme] [2] aborde certains de ces points (rendant les citations plus largement applicables).
Je suis également d'accord avec cela, cependant, comme certains des commentaires dans "New Directions for TH" l'observent, le manque de bonnes citations AST prêtes à l'emploi n'est pas un défaut critique. Dans ce package WIP, je cherche à résoudre ces problèmes sous forme de bibliothèque: https://github.com/mgsloan/quasi-extras . Jusqu'à présent, j'autorise l'épissage à quelques endroits de plus que d'habitude et je peux faire correspondre les motifs sur les AST.
- La restriction de scène est l'enfer. Ne pas pouvoir épisser des fonctions définies dans le même module est la plus petite partie de celui-ci: l'autre conséquence est que si vous avez une épissure de niveau supérieur, tout ce qui se trouve après dans le module sera hors de portée de tout ce qui le précède. D'autres langages avec cette propriété (C, C ++) la rendent réalisable en vous permettant de transmettre des choses, mais pas Haskell. Si vous avez besoin de références cycliques entre des déclarations épissées ou leurs dépendances et dépendances, vous êtes généralement juste foutu.
J'ai rencontré le problème des définitions cycliques de TH qui étaient impossibles auparavant ... C'est assez ennuyeux. Il y a une solution, mais c'est moche - envelopper les choses impliquées dans la dépendance cyclique dans une expression TH qui combine toutes les déclarations générées. L'un de ces générateurs de déclarations pourrait simplement être un quasi-guillemet qui accepte le code Haskell.
- C'est sans principes. Ce que je veux dire par là, c'est que la plupart du temps, lorsque vous exprimez une abstraction, il y a une sorte de principe ou de concept derrière cette abstraction. Pour de nombreuses abstractions, le principe sous-jacent peut être exprimé dans leurs types. Lorsque vous définissez une classe de type, vous pouvez souvent formuler des lois auxquelles les instances doivent obéir et que les clients peuvent assumer. Si vous utilisez la [nouvelle fonctionnalité générique] de GHC [3] pour résumer la forme d'une déclaration d'instance sur n'importe quel type de données (dans des limites), vous pouvez dire "pour les types de somme, cela fonctionne comme ceci, pour les types de produits, cela fonctionne comme ça ". Mais Template Haskell n'est que des macros stupides. Ce n'est pas l'abstraction au niveau des idées, mais l'abstraction au niveau des AST, ce qui est mieux, mais modestement, que l'abstraction au niveau du texte brut.
Ce n'est sans principe que si vous faites des choses sans principe avec. La seule différence est qu'avec les mécanismes d'abstraction mis en œuvre par le compilateur, vous avez plus de confiance que l'abstraction n'est pas étanche. Peut-être que la démocratisation de la conception du langage semble un peu effrayante! Les créateurs de bibliothèques TH doivent bien documenter et définir clairement la signification et les résultats des outils qu'ils fournissent. Un bon exemple de TH basé sur des principes est le paquet dérivé: http://hackage.haskell.org/package/derive - il utilise un DSL tel que l'exemple de nombreuses dérivations / spécifie / la dérivation réelle.
- Cela vous lie au GHC. En théorie, un autre compilateur pourrait l'implémenter, mais dans la pratique, je doute que cela se produise. (Cela contraste avec les différentes extensions de système de type qui, bien qu'elles ne puissent être implémentées que par GHC pour le moment, je pourrais facilement imaginer qu'elles soient adoptées par d'autres compilateurs plus tard et finalement normalisées.)
C'est un très bon point - l'API TH est assez grande et maladroite. La réimplémentation semble être difficile. Cependant, il n'y a vraiment que quelques façons de résoudre le problème de la représentation des AST Haskell. J'imagine que la copie des TH ADT et l'écriture d'un convertisseur vers la représentation AST interne vous aideraient beaucoup. Cela équivaudrait à l'effort (non négligeable) de création de haskell-src-meta. Il pourrait également être simplement réimplémenté en imprimant le TH AST et en utilisant l'analyseur interne du compilateur.
Bien que je puisse me tromper, je ne vois pas TH comme étant aussi compliqué qu'une extension de compilateur, du point de vue de l'implémentation. C'est en fait l'un des avantages de "garder les choses simples" et de ne pas avoir la couche fondamentale d'un système de modèle théoriquement attrayant et statiquement vérifiable.
- L'API n'est pas stable. Lorsque de nouvelles fonctionnalités de langage sont ajoutées à GHC et que le package template-haskell est mis à jour pour les prendre en charge, cela implique souvent des modifications incompatibles en amont des types de données TH. Si vous voulez que votre code TH soit compatible avec plus d'une seule version de GHC, vous devez être très prudent et éventuellement utiliser
CPP
.
C'est également un bon point, mais quelque peu dramatisé. Bien qu'il y ait eu des ajouts d'API récemment, ils n'ont pas induit de ruptures importantes. De plus, je pense qu'avec la citation AST supérieure que j'ai mentionnée plus tôt, l'API qui doit réellement être utilisée peut être très considérablement réduite. Si aucune construction / correspondance n'a besoin de fonctions distinctes et qu'elles sont plutôt exprimées en littéraux, la plupart de l'API disparaît. De plus, le code que vous écrivez porterait plus facilement vers les représentations AST pour les langages similaires à Haskell.
En résumé, je pense que TH est un outil puissant et semi-négligé. Moins de haine pourrait conduire à un écosystème de bibliothèques plus vivant, encourageant la mise en œuvre de prototypes de fonctionnalités plus linguistiques. Il a été observé que TH est un outil surpuissant, qui peut vous permettre / faire / presque n'importe quoi. Anarchie! Eh bien, je pense que ce pouvoir peut vous permettre de surmonter la plupart de ses limites et de construire des systèmes capables d'approches de méta-programmation assez fondées sur des principes. Cela vaut la peine d'utiliser de vilains hacks pour simuler l'implémentation "correcte", car de cette façon, la conception de l'implémentation "correcte" deviendra progressivement claire.
Dans ma version idéale personnelle de nirvana, une grande partie du langage se déplacerait en fait hors du compilateur, dans des bibliothèques de cette variété. Le fait que les fonctionnalités soient implémentées en tant que bibliothèques n'influence pas fortement leur capacité à faire un résumé fidèle.
Quelle est la réponse typique de Haskell au code passe-partout? Abstraction. Quelles sont nos abstractions préférées? Fonctions et typo!
Les classes de types nous permettent de définir un ensemble de méthodes, qui peuvent ensuite être utilisées dans toutes sortes de fonctions génériques sur cette classe. Cependant, à part cela, la seule façon pour les classes d'éviter le passe-partout est de proposer des "définitions par défaut". Maintenant, voici un exemple d'une fonctionnalité sans principes!
Les ensembles de liaisons minimales ne sont pas déclarables / vérifiables par le compilateur. Cela pourrait conduire à des définitions par inadvertance qui donnent le bas en raison de la récurrence mutuelle.
Malgré la grande commodité et la puissance que cela produirait, vous ne pouvez pas spécifier les paramètres par défaut de la superclasse, en raison d'instances orphelines http://lukepalmer.wordpress.com/2009/01/25/a-world-without-orphans/ Ceux-ci nous permettraient de corriger le hiérarchie numérique gracieusement!
La recherche de capacités de type TH pour les valeurs par défaut des méthodes a conduit à http://www.haskell.org/haskellwiki/GHC.Generics . Bien que ce soit cool, ma seule expérience de débogage de code en utilisant ces génériques était presque impossible, en raison de la taille du type induit pour et de l'ADT aussi compliqué qu'un AST. https://github.com/mgsloan/th-extra/commit/d7784d95d396eb3abdb409a24360beb03731c88c
En d'autres termes, cela allait après les fonctionnalités fournies par TH, mais cela devait soulever un domaine entier du langage, le langage de construction, dans une représentation de système de types. Bien que je puisse le voir bien fonctionner pour votre problème commun, pour les problèmes complexes, il semble enclin à produire une pile de symboles beaucoup plus terrifiant que le piratage TH.
TH vous donne le calcul au niveau de la valeur au moment de la compilation du code de sortie, tandis que les génériques vous obligent à soulever la partie de correspondance de motif / récursivité du code dans le système de types. Bien que cela limite l'utilisateur de quelques manières assez utiles, je ne pense pas que la complexité en vaille la peine.
Je pense que le rejet de TH et de la métaprogrammation de type lisp a conduit à la préférence pour des choses comme les méthodes par défaut plutôt que pour des déclarations d'instances plus flexibles, comme la macro-expansion. La discipline d'éviter les choses qui pourraient conduire à des résultats imprévus est sage, cependant, nous ne devons pas ignorer que le système de type capable de Haskell permet une métaprogrammation plus fiable que dans de nombreux autres environnements (en vérifiant le code généré).