Qu'est-ce qui est si mauvais avec Template Haskell?

252

Il semble que Template Haskell soit souvent considéré par la communauté Haskell comme une commodité malheureuse. Il est difficile de mettre en mots exactement ce que j'ai observé à cet égard, mais considérez ces quelques exemples

J'ai vu divers articles de blog où les gens font des trucs assez soignés avec Template Haskell, permettant une syntaxe plus jolie qui ne serait tout simplement pas possible dans Haskell normal, ainsi qu'une réduction considérable du passe-partout. Alors pourquoi est-ce que Template Haskell est méprisé de cette façon? Qu'est-ce qui le rend indésirable? Dans quelles circonstances devrait-on éviter Template Haskell, et pourquoi?

Dan Burton
la source
56
Je ne suis pas d'accord avec le vote pour proposer; Je pose cette question dans le même esprit que j'ai demandé Qu'est-ce qui est si mauvais avec les E / S paresseuses? et je m'attends à voir des réponses de la même façon. Je suis prêt à reformuler la question si cela peut aider.
Dan Burton
51
@ErikPhilips Pourquoi ne laissez-vous pas les personnes qui fréquentent cette balise décider si elle appartient ici? Il semble que votre seule interaction avec la communauté Haskell soit de poser ses questions.
Gabriel Gonzalez
5
@GabrielGonzalez il est évident avec la question et la réponse actuelles qu'il ne suit pas la FAQ sous quel type de question ne devrais-je pas poser ici: il n'y a pas de problème réel à résoudre . La question n'a pas de problème spécifique au code résolu et est uniquement de nature conceptuelle. Et sur la base de la question, devrais-je éviter le haskell de modèle, il tombe également dans le débordement de pile n'est pas un moteur de recommandation .
Erik Philips
27
@ErikPhilips L'aspect du moteur de recommandation n'est pas pertinent pour cette question, car vous remarquerez que cela fait référence à une décision entre différents outils (par exemple, "dites-moi quelle langue je dois utiliser"). En revanche, je demande simplement des explications concernant le modèle Haskell en particulier, et la FAQ indique "si votre motivation est" je voudrais que les autres m'expliquent [en blanc] ", alors vous êtes probablement d'accord." Comparer avec, par exemple, GOTO toujours considéré comme nuisible?
Dan Burton
29
Voter pour rouvrir. Ce n'est pas parce que c'est une question de niveau supérieur qu'elle n'est pas bonne.
György Andrasek

Réponses:

171

Une des raisons pour éviter le Template Haskell est qu'il n'est pas du tout protégé par type, ce qui va à l'encontre de "l'esprit de Haskell". En voici quelques exemples:

  • Vous n'avez aucun contrôle sur le type de Haskell AST qu'un morceau de code TH générera, au-delà de l'endroit où il apparaîtra; vous pouvez avoir une valeur de type Exp, mais vous ne savez pas si c'est une expression qui représente un [Char]ou un (a -> (forall b . b -> c))ou quoi que ce soit. TH serait plus fiable si l'on pouvait exprimer qu'une fonction ne peut générer que des expressions d'un certain type, ou seulement des déclarations de fonction, ou seulement des modèles de correspondance de constructeur de données, etc.
  • Vous pouvez générer des expressions qui ne se compilent pas. Vous avez généré une expression qui fait référence à une variable libre fooqui n'existe pas? Heureusement, vous ne le verrez que lorsque vous utilisez réellement votre générateur de code, et uniquement dans les circonstances qui déclenchent la génération de ce code particulier. Il est également très difficile de faire des tests unitaires.

TH est également carrément dangereux:

  • Le code qui s'exécute au moment de la compilation peut être arbitraire IO, y compris le lancement de missiles ou le vol de votre carte de crédit. Vous ne voulez pas avoir à parcourir chaque paquet cabale que vous avez téléchargé à la recherche d'exploits TH.
  • TH peut accéder aux fonctions et définitions "module-privé", rompant complètement l'encapsulation dans certains cas.

Ensuite, il y a quelques problèmes qui rendent les fonctions TH moins amusantes à utiliser en tant que développeur de bibliothèque:

  • Le code TH n'est pas toujours composable. Disons que quelqu'un fait un générateur pour les lentilles, et le plus souvent, ce générateur sera structuré de telle sorte qu'il ne puisse être appelé directement que par "l'utilisateur final", et non par un autre code TH, par exemple en prenant une liste de constructeurs de type pour générer des lentilles comme paramètre. Il est difficile de générer cette liste en code, alors que l'utilisateur n'a qu'à écrire generateLenses [''Foo, ''Bar].
  • Les développeurs ne savent même pas que le code TH peut être composé. Saviez-vous que vous pouvez écrire forM_ [''Foo, ''Bar] generateLens? Qest juste une monade, vous pouvez donc utiliser toutes les fonctions habituelles dessus. Certaines personnes ne le savent pas, et à cause de cela, elles créent plusieurs versions surchargées essentiellement des mêmes fonctions avec les mêmes fonctionnalités, et ces fonctions conduisent à un certain effet de ballonnement. De plus, la plupart des gens écrivent leurs générateurs dans la Qmonade même lorsqu'ils n'y sont pas obligés, ce qui revient à écrire bla :: IO Int; bla = return 3; vous donnez à une fonction plus d '«environnement» qu'elle n'en a besoin, et les clients de la fonction sont tenus de fournir cet environnement en conséquence.

Enfin, il y a certaines choses qui rendent les fonctions TH moins amusantes à utiliser en tant qu'utilisateur final:

  • Opacité. Lorsqu'une fonction TH a un type Q Dec, elle peut générer absolument n'importe quoi au niveau supérieur d'un module, et vous n'avez absolument aucun contrôle sur ce qui sera généré.
  • Monolithisme. Vous ne pouvez pas contrôler combien une fonction TH génère à moins que le développeur ne le permette; si vous trouvez une fonction qui génère une interface de base de données et une interface de sérialisation JSON, vous ne pouvez pas dire "Non, je veux seulement l'interface de base de données, merci; je vais rouler ma propre interface JSON"
  • Durée. Le code TH prend un temps relativement long à s'exécuter. Le code est interprété de nouveau à chaque fois qu'un fichier est compilé, et souvent, une tonne de packages sont requis par le code TH en cours d'exécution, qui doivent être chargés. Cela ralentit considérablement le temps de compilation.
dflemstr
la source
4
Ajoutez à cela le fait que template-haskell a été historiquement très mal documenté. (Même si je viens de regarder à nouveau et il semble que les choses soient au moins légèrement meilleures maintenant.) De plus, pour comprendre le modèle-haskell, vous devez essentiellement comprendre la grammaire du langage Haskell, qui impose une certaine complexité (ce n'est pas un schéma). Ces deux choses m'ont aidé à ne pas déranger intentionnellement la TH lorsque j'étais débutant à Haskell.
mightybyte
14
N'oubliez pas que l'utilisation de Template Haskell signifie soudainement que l'ordre des déclarations est important! TH n'est tout simplement pas intégré aussi étroitement que l'on pourrait espérer étant donné le poli lisse de Haskell (que ce soit 1.4, 98, 2010 ou même Glasgow).
Thomas M. DuBuisson
13
Vous pouvez raisonner sur Haskell sans trop de difficulté, il n'y a pas une telle garantie sur Template Haskell.
augustss
15
Et qu'est-il arrivé à la promesse d'Oleg d'une alternative sûre à la TH? Je fais référence à son travail basé sur son article "Enfin sans étiquette, partiellement évalué" et plus de ses notes ici . Cela avait l'air si prometteur quand ils l'ont annoncé et je n'ai jamais entendu un mot à ce sujet.
Gabriel Gonzalez
53

Ce n'est que ma propre opinion.

  • C'est moche à utiliser. $(fooBar ''Asdf)n'a tout simplement pas l'air sympa. Superficiel, bien sûr, mais ça y contribue.

  • 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 est grande et peu maniable, il y a toujours beaucoup de cas dont vous ne vous souciez pas mais que vous devez toujours envoyer, et les cas dont vous vous souciez ont tendance à être présents sous plusieurs formes similaires mais pas identiques (données vs nouveau type, enregistrement -style vs constructeurs normaux, etc.). C'est ennuyeux et répétitif d'écrire et assez compliqué pour ne pas être mécanique. La proposition de réforme aborde certains de ces points (rendant les citations plus largement applicables).

  • 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.

  • C'est indiscipliné. 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. Pour les classes 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 pour abstraire 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". Le modèle Haskell, en revanche, n'est que des macros. 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. *

  • 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.)

  • 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.

  • Il existe un principe général selon lequel vous devez utiliser le bon outil pour le travail et le plus petit qui suffira, et dans cette analogie, le modèle Haskell est quelque chose comme ça . S'il y a un moyen de le faire qui n'est pas Template Haskell, c'est généralement préférable.

L'avantage de Template Haskell est que vous pouvez faire avec lui des choses que vous ne pourriez pas faire autrement, et c'est un gros problème. La plupart du temps, les choses pour lesquelles TH est utilisé ne pourraient autrement être faites que si elles étaient implémentées directement en tant que fonctionnalités du compilateur. TH est extrêmement avantageux d'avoir à la fois parce qu'il vous permet de faire ces choses et parce qu'il vous permet de prototyper les extensions potentielles du compilateur d'une manière beaucoup plus légère et réutilisable (voir les différents packages de lentilles, par exemple).

Pour résumer pourquoi je pense qu'il y a des sentiments négatifs envers Template Haskell: il résout beaucoup de problèmes, mais pour tout problème donné qu'il résout, il semble qu'il devrait y avoir une meilleure solution, plus élégante et disciplinée, mieux adaptée pour résoudre ce problème, celui qui ne résout pas le problème en générant automatiquement le passe-partout, mais en supprimant la nécessité d' avoir le passe-partout.

* Bien que je pense souvent que le CPPrapport poids / puissance est meilleur pour les problèmes qu'il peut résoudre.

EDIT 23-04-14: Ce que j'essayais fréquemment de faire dans ce qui précède, et que je n'ai obtenu que récemment, c'est qu'il y a une distinction importante entre abstraction et déduplication. Une abstraction correcte entraîne souvent la déduplication comme effet secondaire, et la duplication est souvent un signe révélateur d'une abstraction inadéquate, mais ce n'est pas pourquoi elle est valable. Une abstraction correcte est ce qui rend le code correct, compréhensible et maintenable. La déduplication ne fait que la raccourcir. Le modèle Haskell, comme les macros en général, est un outil de déduplication.

glaebhoerl
la source
"Le CPP a un meilleur rapport puissance / poids pour les problèmes qu'il peut résoudre". En effet. Et en C99, il peut résoudre tout ce que vous voulez. Considérez-les: COS , Chaos . Je ne comprends pas non plus pourquoi les gens pensent que la génération AST est meilleure. C'est juste plus d'ambiguïté et moins orthogonal à d'autres fonctionnalités linguistiques
Britton Kerin
31

Je voudrais aborder quelques-uns des points soulevés par dflemstr.

Je ne trouve pas le fait que vous ne puissiez pas vérifier le TH comme si inquiétant. Pourquoi? Parce que même s'il y a une erreur, ce sera toujours le temps de compilation. Je ne sais pas si cela renforce mon argument, mais cela est similaire dans l'esprit aux erreurs que vous recevez lors de l'utilisation de modèles en C ++. Je pense que ces erreurs sont plus compréhensibles que les erreurs de C ++, car vous obtiendrez une version assez imprimée du code généré.

Si une expression TH / un quasi-guillemet fait quelque chose de si avancé que des coins difficiles peuvent se cacher, alors peut-être est-il mal avisé?

Je brise un peu cette règle avec les quasi-guillemets sur lesquels j'ai travaillé récemment (en utilisant haskell-src-exts / meta) - https://github.com/mgsloan/quasi-extras/tree/master/examples . Je sais que cela introduit certains bugs comme le fait de ne pas pouvoir épisser dans les listes de compréhension généralisées. Cependant, je pense qu'il y a de fortes chances que certaines des idées de http://hackage.haskell.org/trac/ghc/blog/Template%20Haskell%20Proposal se retrouvent dans le compilateur. Jusque-là, les bibliothèques pour analyser Haskell en arbres TH sont une approximation presque parfaite.

En ce qui concerne la vitesse / les dépendances de compilation, nous pouvons utiliser le package "zeroth" pour incorporer le code généré. C'est au moins agréable pour les utilisateurs d'une bibliothèque donnée, mais nous ne pouvons pas faire mieux pour le cas de l'édition de la bibliothèque. Les dépendances TH peuvent-elles gonfler les binaires générés? Je pensais que cela laissait de côté tout ce qui n'était pas référencé par le code compilé.

La restriction de mise en scène / fractionnement des étapes de compilation du module Haskell est nul.

RE Opacity: C'est la même chose pour toute fonction de bibliothèque que vous appelez. Vous n'avez aucun contrôle sur ce que Data.List.groupBy fera. Vous avez juste une "garantie" / convention raisonnable que les numéros de version vous disent quelque chose sur la compatibilité. C'est un peu une question de changement différente quand.

C'est là que l'utilisation de zeroth est payante - vous êtes déjà en train de versionner les fichiers générés - donc vous saurez toujours quand la forme du code généré a changé. En regardant les différences, cela peut être un peu noueux, cependant, pour de grandes quantités de code généré, c'est donc un endroit où une meilleure interface de développeur serait pratique.

Monolithisme RE: vous pouvez certainement post-traiter les résultats d'une expression TH, en utilisant votre propre code de compilation. Ce ne serait pas beaucoup de code à filtrer sur le type / nom de déclaration de niveau supérieur. Heck, vous pourriez imaginer écrire une fonction qui le fait de manière générique. Pour modifier / démonolithiser les quasiquoteurs, vous pouvez faire correspondre les motifs sur "QuasiQuoter" et extraire les transformations utilisées, ou en créer une nouvelle en fonction de l'ancienne.

mgsloan
la source
1
Concernant l' opacité / le monolithisme: vous pouvez bien sûr parcourir [Dec]et supprimer des éléments que vous ne voulez pas, mais disons que la fonction lit un fichier de définition externe lors de la génération de l'interface JSON. Ce n'est pas parce que vous n'utilisez pas que Decle générateur arrête de chercher le fichier de définition, ce qui fait échouer la compilation. Pour cette raison, ce serait bien d'avoir une version plus restrictive de la Qmonade qui vous permettrait de générer de nouveaux noms (et de telles choses) mais pas de permettre IO, de sorte que, comme vous le dites, on pourrait filtrer ses résultats / faire d'autres compositions .
dflemstr
1
Je suis d'accord, il devrait y avoir une version non-IO de Q / devis! Cela aiderait également à résoudre - stackoverflow.com/questions/7107308/… . Une fois que vous vous êtes assuré que le code à la compilation ne fait rien de dangereux, il semble que vous puissiez simplement exécuter le vérificateur de sécurité sur le code résultant et vous assurer qu'il ne fait pas référence à des éléments privés.
mgsloan
1
Avez-vous déjà rédigé votre contre-argument? Toujours en attente de votre lien promis. Pour ceux qui recherchent l'ancien contenu de cette réponse: stackoverflow.com/revisions/10859441/1 , et pour ceux qui peuvent afficher le contenu supprimé: stackoverflow.com/revisions/10913718/6
Dan Burton
1
existe-t-il une version plus récente de zeroth que celle sur le piratage? Celui-ci (et le dépôt darcs) ont été mis à jour pour la dernière fois en 2009. Les deux copies ne sont pas construites avec un ghc actuel (7.6).
aavogt
1
@aavogt tgeeky travaillait à le réparer, mais je ne pense pas qu'il ait terminé: github.com/technogeeky/zeroth Si personne ne le fait / ne fait quelque chose pour ça, je vais y arriver éventuellement.
mgsloan
16

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é).

mgsloan
la source
4
Cette réponse ne suffit pas très bien: vous faites un tas de références à une autre réponse que je dois aller chercher avant de pouvoir lire la vôtre correctement.
Ben Millwood
C'est vrai. J'ai essayé de préciser ce dont on parlait malgré. Je vais peut-être éditer pour intégrer les points d'illisuis.
mgsloan
Je devrais dire que peut-être "sans principes" est un mot plus fort que j'aurais dû utiliser, avec des connotations que je n'avais pas l'intention - ce n'est certainement pas sans scrupules! Ce point a été celui avec lequel j'ai eu le plus de problèmes, car j'ai ce sentiment ou cette idée non formulée dans ma tête et j'ai du mal à le mettre en mots, et "sans principes" est l'un des mots que j'ai saisis quelque part dans les environs. "Discipliné" est probablement plus proche. N'ayant pas de formulation claire et succincte, j'ai plutôt choisi quelques exemples. Si quelqu'un pouvait m'expliquer plus clairement ce que je voulais dire, j'apprécierais!
glaebhoerl
(Je pense également que vous avez peut-être changé la restriction de la mise en scène et les citations laides à écrire.)
glaebhoerl
1
Ah! Merci d'avoir fait remarquer cela! Fixé. Oui, indiscipliné serait probablement une meilleure façon de le dire. Je pense que la distinction ici est vraiment entre les mécanismes d'abstraction «intégrés», et donc «bien compris», et les mécanismes d'abstraction «ad hoc» ou définis par l'utilisateur. Je suppose que ce que je veux dire est que vous pourriez définir une bibliothèque TH qui implémenterait quelque chose d'assez similaire à la répartition des classes (bien qu'à la compilation et non à l'exécution)
mgsloan
8

Un problème assez pragmatique avec Template Haskell est qu'il ne fonctionne que lorsque l'interpréteur de bytecode de GHC est disponible, ce qui n'est pas le cas sur toutes les architectures. Donc, si votre programme utilise Template Haskell ou s'appuie sur des bibliothèques qui l'utilisent, il ne fonctionnera pas sur les machines avec un processeur ARM, MIPS, S390 ou PowerPC.

Ceci est pertinent dans la pratique: git-annex est un outil écrit en Haskell qui a du sens pour fonctionner sur des machines soucieuses de stockage, ces machines ont souvent des processeurs non i386. Personnellement, je lance git-annex sur un NSLU 2 (32 Mo de RAM, CPU 266 MHz; saviez-vous que Haskell fonctionne bien sur un tel matériel?) S'il utiliserait le modèle Haskell, ce n'est pas possible.

(La situation concernant GHC sur ARM s'améliore beaucoup ces jours-ci et je pense que 7.4.2 fonctionne même, mais le point est toujours valable).

Joachim Breitner
la source
1
«Le modèle Haskell s'appuie sur le compilateur et interprète de bytecode intégré de GHC pour exécuter les expressions d'épissage.» - haskell.org/ghc/docs/7.6.2/html/users_guide/…
Joachim Breitner
2
ah, je ça - non, TH ne fonctionnera pas sans l'interpréteur de bytecode, mais c'est différent de (bien que pertinent pour) ghci. Je ne serais pas surpris s'il y avait une relation parfaite entre la disponibilité de ghci et la disponibilité de l'interpréteur de bytecode, étant donné que ghci dépend de l'interpréteur de bytecode, mais le problème est le manque de l'interpréteur de bytecode, pas le manque de ghci Plus précisément.
muhmuhten
1
(accessoirement, ghci a un support étonnamment faible pour une utilisation interactive de TH. try ghci -XTemplateHaskell <<< '$(do Language.Haskell.TH.runIO $ (System.Random.randomIO :: IO Int) >>= print; [| 1 |] )')
muhmuhten
Ok, avec ghci, je faisais référence à la capacité du GHC à interpréter (au lieu de compiler) du code, indépendamment du fait que le code soit interactif dans le binaire ghci ou de l'épissure TH.
Joachim Breitner
6

Pourquoi TH est-il mauvais? Pour moi, cela se résume à ceci:

Si vous avez besoin de produire tellement de code répétitif que vous vous retrouvez à essayer d'utiliser TH pour le générer automatiquement, vous le faites mal!

Pensez-y. La moitié de l'attrait de Haskell est que sa conception de haut niveau vous permet d'éviter d'énormes quantités de code passe-partout inutile que vous devez écrire dans d'autres langues. Si vous avez besoin de génération de code au moment de la compilation, vous dites essentiellement que votre langue ou la conception de votre application vous a échoué. Et nous, les programmeurs, n'aimons pas échouer.

Parfois, bien sûr, c'est nécessaire. Mais parfois, vous pouvez éviter d'avoir besoin de TH en étant un peu plus intelligent avec vos conceptions.

(L'autre chose est que TH est assez bas niveau. Il n'y a pas de grande conception de haut niveau; beaucoup de détails d'implémentation internes de GHC sont exposés. Et cela rend l'API sujette à changement ...)

MathematicalOrchid
la source
Je ne pense pas que cela signifie que votre langue ou application vous a échoué, surtout si nous considérons QuasiQuotes. Il y a toujours des compromis à faire en matière de syntaxe. Certaines syntaxes décrivent mieux un certain domaine, vous devez donc pouvoir parfois passer à une autre syntaxe. QuasiQuotes vous permet de basculer avec élégance entre les syntaxes. C'est très puissant et est utilisé par Yesod et d'autres applications. Être capable d'écrire du code de génération HTML en utilisant une syntaxe qui ressemble à du HTML est une fonctionnalité étonnante.
CoolCodeBro
@CoolCodeBro Oui, la quasi-citation est plutôt sympa, et je suppose légèrement orthogonale à TH. (Évidemment, il est implémenté au-dessus de TH.) Je pensais davantage à l'utilisation de TH pour générer des instances de classe, ou créer des fonctions de plusieurs arités, ou quelque chose comme ça.
MathematicalOrchid