Was ist so schlimm an Template Haskell?


252

Es scheint, dass Template Haskell von der Haskell-Community oft als unglückliche Annehmlichkeit angesehen wird. Es ist schwer, genau in Worte zu fassen, was ich in dieser Hinsicht beobachtet habe, aber betrachten Sie diese wenigen Beispiele

Ich habe verschiedene Blog-Posts gesehen, in denen Leute mit Template Haskell ziemlich nette Sachen machen, die eine schönere Syntax ermöglichen, die in normalem Haskell einfach nicht möglich wäre, sowie eine enorme Reduzierung der Boilerplate. Warum wird Template Haskell auf diese Weise herabgesehen? Was macht es unerwünscht? Unter welchen Umständen sollte Template Haskell vermieden werden und warum?


56
Ich bin mit der Abstimmung nicht einverstanden, um zu bewegen; Ich stelle diese Frage in dem Geist, in dem ich sie gestellt habe. Was ist so schlimm an Lazy I / O? und ich erwarte Antworten auf die gleiche Weise. Ich bin offen für eine Neuformulierung der Frage, ob dies helfen würde.
Dan Burton

51
@ErikPhilips Warum lässt du nicht die Leute, die dieses Tag besuchen, entscheiden, ob es hierher gehört? Es scheint, als ob Ihre einzige Interaktion mit der Haskell-Community darin besteht, ihre Fragen niederzulegen.
Gabriel Gonzalez

5
@GabrielGonzalez es ist offensichtlich mit der aktuellen Frage und Antwort, dass es nicht den FAQ folgt, unter welcher Art von Frage sollte ich hier nicht stellen: Es gibt kein tatsächlich zu lösendes Problem . Die Frage hat kein codespezifisches Problem gelöst und ist nur konzeptioneller Natur. Und basierend auf der Frage, ob ich Template-Haskell vermeiden soll, fällt es auch in den Stack Overflow, ist keine Empfehlungs-Engine .
Erik Philips

27
@ErikPhilips Der Aspekt der Empfehlungs-Engine ist für diese Frage irrelevant, da Sie feststellen werden, dass sich dies auf eine Entscheidung zwischen verschiedenen Tools bezieht (z. B. "Sagen Sie mir, welche Sprache ich verwenden soll"). Im Gegensatz dazu bitte ich nur um eine Erklärung bezüglich Template Haskell, und in den FAQ heißt es: "Wenn Ihre Motivation lautet" Ich möchte, dass andere mir [leer] erklären ", dann sind Sie wahrscheinlich in Ordnung." Vergleichen Sie zum Beispiel mit GOTO, das immer noch als schädlich eingestuft wird?
Dan Burton

29
Abstimmung zur Wiedereröffnung. Nur weil es sich um eine übergeordnete Frage handelt, heißt das nicht, dass es keine gute ist.
György Andrasek

Antworten:


171

Ein Grund für die Vermeidung von Template Haskell ist, dass es als Ganzes überhaupt nicht typsicher ist und somit gegen einen Großteil des "Geistes von Haskell" verstößt. Hier einige Beispiele dafür:

  • Sie haben keine Kontrolle darüber, welche Art von Haskell-AST ein Stück TH-Code generiert, jenseits dessen, wo er angezeigt wird. Sie können einen Wert vom Typ haben Exp, wissen aber nicht, ob es sich um einen Ausdruck handelt, der ein [Char]oder ein (a -> (forall b . b -> c))oder was auch immer darstellt. TH wäre zuverlässiger, wenn man ausdrücken könnte, dass eine Funktion nur Ausdrücke eines bestimmten Typs oder nur Funktionsdeklarationen oder nur Datenkonstruktor-Übereinstimmungsmuster usw. erzeugen kann.
  • Sie können Ausdrücke generieren, die nicht kompiliert werden. Sie haben einen Ausdruck generiert, der auf eine freie Variable fooverweist, die nicht vorhanden ist? Pech, Sie werden das nur sehen, wenn Sie Ihren Codegenerator tatsächlich verwenden, und nur unter den Umständen, die die Generierung dieses bestimmten Codes auslösen. Es ist auch sehr schwierig, einen Unit-Test durchzuführen.

TH ist auch geradezu gefährlich:

  • Code, der zur Kompilierungszeit ausgeführt wird, kann beliebig ausgeführt werden IO, einschließlich des Abschusses von Raketen oder des Diebstahls Ihrer Kreditkarte. Sie möchten nicht jedes Kabalenpaket durchsuchen müssen, das Sie jemals heruntergeladen haben, um nach TH-Exploits zu suchen.
  • TH kann auf "modulprivate" Funktionen und Definitionen zugreifen, wodurch die Kapselung in einigen Fällen vollständig unterbrochen wird.

Dann gibt es einige Probleme, die die Verwendung von TH-Funktionen als Bibliotheksentwickler weniger unterhaltsam machen:

  • TH-Code ist nicht immer zusammensetzbar. Nehmen wir an, jemand stellt einen Generator für Objektive her, und meistens wird dieser Generator so strukturiert, dass er nur direkt vom "Endbenutzer" und nicht von anderem TH-Code aufgerufen werden kann, beispielsweise durch Nehmen Eine Liste von Typkonstruktoren, für die Linsen als Parameter generiert werden sollen. Es ist schwierig, diese Liste im Code zu generieren, während der Benutzer nur schreiben muss generateLenses [''Foo, ''Bar].
  • Entwickler wissen nicht einmal , dass TH-Code komponiert werden kann. Wussten Sie, dass Sie schreiben können forM_ [''Foo, ''Bar] generateLens? Qist nur eine Monade, so dass Sie alle üblichen Funktionen darauf verwenden können. Einige Leute wissen das nicht und erstellen aus diesem Grund mehrere überladene Versionen von im Wesentlichen denselben Funktionen mit derselben Funktionalität, und diese Funktionen führen zu einem gewissen Aufblähungseffekt. Außerdem schreiben die meisten Leute ihre Generatoren in die QMonade, auch wenn sie es nicht müssen, was wie Schreiben ist bla :: IO Int; bla = return 3; Sie geben einer Funktion mehr "Umgebung" als sie benötigt, und Clients der Funktion müssen diese Umgebung als Folge davon bereitstellen.

Schließlich gibt es einige Dinge, die die Verwendung von TH-Funktionen als Endbenutzer weniger unterhaltsam machen:

  • Opazität. Wenn eine TH-Funktion vom Typ ist Q Dec, kann sie auf der obersten Ebene eines Moduls absolut alles generieren, und Sie haben absolut keine Kontrolle darüber, was generiert wird.
  • Monolithismus. Sie können nicht steuern, wie viel eine TH-Funktion generiert, es sei denn, der Entwickler lässt dies zu. Wenn Sie eine Funktion finden, die eine Datenbankschnittstelle und eine JSON-Serialisierungsschnittstelle generiert , können Sie nicht sagen: "Nein, ich möchte nur die Datenbankschnittstelle, danke. Ich werde meine eigene JSON-Schnittstelle rollen."
  • Laufzeit. Die Ausführung von TH-Code dauert relativ lange. Der Code wird jedes Mal neu interpretiert, wenn eine Datei kompiliert wird, und häufig benötigt der laufende TH-Code eine Menge Pakete, die geladen werden müssen. Dies verlangsamt die Kompilierungszeit erheblich.

4
Hinzu kommt, dass Template-Haskell historisch sehr schlecht dokumentiert wurde. (Obwohl ich nur noch einmal nachgesehen habe und es den Anschein hat, dass die Dinge jetzt zumindest geringfügig besser sind.) Um Template-Haskell zu verstehen, muss man im Wesentlichen die Grammatik der Haskell-Sprache verstehen, die eine gewisse Komplexität auferlegt (es ist kein Schema). Diese beiden Dinge haben dazu beigetragen, dass ich mich als Haskell-Anfänger absichtlich nicht darum gekümmert habe, TH zu verstehen.
Mächtiges

14
Vergessen Sie nicht, dass die Verwendung von Template Haskell plötzlich die Reihenfolge der Deklarationen bedeutet! TH ist einfach nicht so eng integriert, wie man angesichts der glatten Politur von Haskell hoffen könnte (sei es 1.4, '98, 2010 oder sogar Glasgow).
Thomas M. DuBuisson

13
Sie können ohne allzu große Schwierigkeiten über Haskell nachdenken. Es gibt keine solche Garantie für Template Haskell.
August

15
Und was ist mit Olegs Versprechen einer typsicheren Alternative zu TH passiert? Ich beziehe mich auf seine Arbeit, die auf seinem Artikel "Endlich taglos, teilweise bewertet" und weiteren Notizen hier basiert . Es sah so vielversprechend aus, als sie es ankündigten, und dann hörte ich nie wieder ein Wort darüber.
Gabriel Gonzalez


53

Dies ist ausschließlich meine eigene Meinung.

  • Es ist hässlich zu benutzen. $(fooBar ''Asdf)sieht einfach nicht gut aus. Oberflächlich, sicher, aber es trägt dazu bei.

  • Es ist noch hässlicher zu schreiben. Das Zitieren funktioniert manchmal, aber die meiste Zeit müssen Sie manuell AST-Transplantationen und -Installationen durchführen. Das API ist groß und unhandlich, es gibt immer viele Fälle, die Sie nicht interessieren, aber dennoch versenden müssen, und die Fälle, die Sie interessieren, sind in der Regel in mehreren ähnlichen, aber nicht identischen Formen vorhanden (Daten vs. Newtype, Datensatz) -Stil gegen normale Konstruktoren und so weiter). Es ist langweilig und sich wiederholend zu schreiben und kompliziert genug, um nicht mechanisch zu sein. Der Reformvorschlag befasst sich mit einigen dieser Fragen (wodurch Zitate allgemeiner anwendbar werden).

  • Die Bühnenbeschränkung ist die Hölle. Die Möglichkeit, Funktionen, die im selben Modul definiert sind, nicht zu verbinden, ist der kleinere Teil davon. Die andere Konsequenz ist, dass, wenn Sie einen Spleiß der obersten Ebene haben, alles, was danach im Modul vorhanden ist, für alles, was davor liegt, außer Reichweite ist. Andere Sprachen mit dieser Eigenschaft (C, C ++) machen es funktionsfähig, indem Sie deklarierte Dinge weiterleiten können, Haskell jedoch nicht. Wenn Sie zyklische Verweise zwischen gespleißten Deklarationen oder deren Abhängigkeiten und Abhängigkeiten benötigen, werden Sie normalerweise nur geschraubt.

  • Es ist undiszipliniert. Damit meine ich, dass die meiste Zeit, wenn Sie eine Abstraktion ausdrücken, hinter dieser Abstraktion ein Prinzip oder ein Konzept steckt. Für viele Abstraktionen kann das Prinzip dahinter in ihren Typen ausgedrückt werden. Für Typklassen können Sie häufig Gesetze formulieren, denen Instanzen gehorchen sollten und die Clients annehmen können. Wenn Sie die neue Generika-Funktion von GHC verwenden von um die Form einer Instanzdeklaration über einen beliebigen Datentyp (innerhalb von Grenzen) zu abstrahieren, können Sie sagen: "Für Summentypen funktioniert das so, für Produkttypen funktioniert es so". Template Haskell hingegen ist nur ein Makro. Es ist keine Abstraktion auf der Ebene der Ideen, sondern eine Abstraktion auf der Ebene der ASTs, die besser, aber nur bescheiden ist als die Abstraktion auf der Ebene des Klartextes. *

  • Es bindet Sie an GHC. Theoretisch könnte es ein anderer Compiler implementieren, aber in der Praxis bezweifle ich, dass dies jemals passieren wird. (Dies steht im Gegensatz zu verschiedenen Typ-Systemerweiterungen, die, obwohl sie derzeit möglicherweise nur von GHC implementiert werden, leicht vorstellbar sind, später von anderen Compilern übernommen und schließlich standardisiert zu werden.)

  • Die API ist nicht stabil. Wenn GHC neue Sprachfunktionen hinzugefügt und das Template-Haskell-Paket aktualisiert wird, um diese zu unterstützen, sind häufig abwärtsinkompatible Änderungen an den TH-Datentypen erforderlich. Wenn Sie möchten, dass Ihr TH-Code mit mehr als nur einer Version von GHC kompatibel ist, müssen Sie sehr vorsichtig sein und möglicherweise verwenden CPP.

  • Es gibt ein allgemeines Prinzip, dass Sie das richtige Werkzeug für den Job verwenden sollten und das kleinste, das ausreicht, und in dieser Analogie ist Vorlage Haskell so etwas . Wenn es eine Möglichkeit gibt, die nicht Template Haskell ist, ist dies im Allgemeinen vorzuziehen.

Der Vorteil von Template Haskell ist, dass Sie damit Dinge tun können, die Sie auf keine andere Weise tun könnten, und es ist eine große Sache. Meistens könnten die Dinge, für die TH verwendet wird, sonst nur ausgeführt werden, wenn sie direkt als Compiler-Funktionen implementiert würden. TH ist äußerst vorteilhaft, da Sie damit diese Aufgaben ausführen und potenzielle Compiler-Erweiterungen viel leichter und wiederverwendbarer prototypisieren können (siehe z. B. die verschiedenen Objektivpakete).

Um zusammenzufassen, warum ich denke, dass es negative Gefühle gegenüber Template Haskell gibt: Es löst viele Probleme, aber für jedes gegebene Problem, das es löst, scheint es eine bessere, elegantere, diszipliniertere Lösung zu geben, die besser zur Lösung dieses Problems geeignet ist. eine , die das Problem nicht , indem sie automatisch die vorformulierten Erzeugung, sondern durch die Beseitigung der Notwendigkeit löst haben die vorformulierten.

* Obwohl ich oft das Gefühl habe, dass CPPdas Leistungsgewicht für die Probleme, die es lösen kann, besser ist.

EDIT 23-04-14: Was ich oben häufig versucht habe und erst kürzlich genau erreicht habe, ist, dass es einen wichtigen Unterschied zwischen Abstraktion und Deduplizierung gibt. Die richtige Abstraktion führt häufig zu einer Deduplizierung als Nebeneffekt, und die Duplizierung ist oft ein verräterisches Zeichen für eine unzureichende Abstraktion, aber das ist nicht der Grund, warum sie wertvoll ist. Die richtige Abstraktion macht Code korrekt, verständlich und wartbar. Die Deduplizierung macht es nur kürzer. Template Haskell ist wie Makros im Allgemeinen ein Tool zur Deduplizierung.


"CPP hat ein besseres Leistungsgewicht für die Probleme, die es lösen kann". Tatsächlich. Und in C99 kann es lösen, was Sie wollen. Betrachten Sie diese: COS , Chaos . Ich verstehe auch nicht, warum die Leute denken, dass die AST-Generation besser ist. Es ist nur mehrdeutig und weniger orthogonal zu anderen Sprachmerkmalen
Britton Kerin

31

Ich möchte einige der Punkte ansprechen, die dflemstr anspricht.

Ich finde die Tatsache, dass Sie TH nicht tippen können, nicht so besorgniserregend. Warum? Denn selbst wenn ein Fehler auftritt, bleibt die Kompilierungszeit. Ich bin nicht sicher, ob dies meine Argumentation stärkt, aber dies ähnelt im Geiste den Fehlern, die Sie bei der Verwendung von Vorlagen in C ++ erhalten. Ich denke, diese Fehler sind verständlicher als die Fehler von C ++, da Sie eine hübsche gedruckte Version des generierten Codes erhalten.

Wenn ein TH-Ausdruck / Quasi-Quoter etwas tut, das so weit fortgeschritten ist, dass sich schwierige Ecken verbergen können, ist es vielleicht schlecht beraten?

Ich verstoße ziemlich oft gegen diese Regel mit Quasi-Quotern, an denen ich in letzter Zeit gearbeitet habe (mit haskell-src-exts / meta) - https://github.com/mgsloan/quasi-extras/tree/master/examples . Ich weiß, dass dies einige Fehler mit sich bringt, z. B., dass das verallgemeinerte Listenverständnis nicht gespleißt werden kann. Ich denke jedoch, dass es eine gute Chance gibt, dass einige der Ideen in http://hackage.haskell.org/trac/ghc/blog/Template%20Haskell%20Proposal im Compiler landen. Bis dahin sind die Bibliotheken zum Parsen von Haskell auf TH-Bäume eine nahezu perfekte Annäherung.

In Bezug auf die Kompilierungsgeschwindigkeit / -abhängigkeiten können wir das "nullte" Paket verwenden, um den generierten Code zu integrieren. Dies ist zumindest für die Benutzer einer bestimmten Bibliothek von Vorteil, aber wir können es beim Bearbeiten der Bibliothek nicht viel besser machen. Können TH-Abhängigkeiten generierte Binärdateien aufblähen? Ich dachte, es würde alles ausgelassen, worauf der kompilierte Code nicht verweist.

Die Staging-Einschränkung / Aufteilung der Kompilierungsschritte des Haskell-Moduls ist zum Kotzen.

RE-Deckkraft: Dies gilt auch für alle von Ihnen aufgerufenen Bibliotheksfunktionen. Sie haben keine Kontrolle darüber, was Data.List.groupBy tun wird. Sie haben nur eine vernünftige "Garantie" / Konvention, dass die Versionsnummern etwas über die Kompatibilität aussagen. Es ist eine etwas andere Sache der Veränderung, wenn.

Hier zahlt sich die Verwendung von null aus - Sie versionieren bereits die generierten Dateien -, sodass Sie immer wissen, wann sich die Form des generierten Codes geändert hat. Das Betrachten der Unterschiede kann jedoch für große Mengen an generiertem Code etwas knorrig sein. Dies ist also ein Ort, an dem eine bessere Entwickleroberfläche nützlich wäre.

RE-Monolithismus: Sie können die Ergebnisse eines TH-Ausdrucks mit Ihrem eigenen Code zur Kompilierungszeit nachbearbeiten. Es wäre nicht sehr viel Code, nach Deklarationstyp / -name der obersten Ebene zu filtern. Sie können sich vorstellen, eine Funktion zu schreiben, die dies generisch tut. Zum Modifizieren / Entmonolithisieren von Quasiquotern können Sie die Musterübereinstimmung auf "QuasiQuoter" durchführen und die verwendeten Transformationen extrahieren oder eine neue in Bezug auf die alten erstellen.


1
In Bezug auf Deckkraft / Monolithismus: Sie können natürlich durch ein Element gehen [Dec]und Dinge entfernen, die Sie nicht möchten, aber sagen wir, dass die Funktion beim Generieren der JSON-Schnittstelle eine externe Definitionsdatei liest. Nur weil Sie dies nicht verwenden, Dechört der Generator nicht auf, nach der Definitionsdatei zu suchen, und die Kompilierung schlägt fehl. Aus diesem Grund wäre es schön, eine restriktivere Version der QMonade zu haben, mit der Sie neue Namen (und solche Dinge) generieren, aber nicht zulassen können IO, damit Sie, wie Sie sagen, die Ergebnisse filtern / andere Kompositionen erstellen können .
dflemstr

1
Ich bin damit einverstanden, dass es eine Nicht-E / A-Version von Q / Zitat geben sollte! Dies würde auch zur Lösung beitragen - stackoverflow.com/questions/7107308/… . Sobald Sie sichergestellt haben, dass der Code zur Kompilierungszeit nichts Unsicheres bewirkt, können Sie anscheinend einfach die Sicherheitsüberprüfung für den resultierenden Code ausführen und sicherstellen, dass er nicht auf private Inhalte verweist.
mgsloan

1
Haben Sie jemals Ihr Gegenargument geschrieben? Ich warte immer noch auf deinen versprochenen Link. Für diejenigen, die nach dem alten Inhalt dieser Antwort suchen : stackoverflow.com/revisions/10859441/1 , und für diejenigen, die gelöschten Inhalt anzeigen
Dan Burton

1
Gibt es eine aktuellere Version von Null als die Version von Hackage? Dieses (und das Darcs-Repo) wurden zuletzt im Jahr 2009 aktualisiert. Beide Kopien werden nicht mit einem aktuellen ghc (7.6) erstellt.
Aavogt

1
@aavogt tgeeky hat daran gearbeitet, es zu reparieren, aber ich glaube nicht, dass er fertig ist: github.com/technogeeky/zeroth Wenn niemand es tut / etwas dafür macht, werde ich mich irgendwann darum kümmern.
mgsloan

16

Diese Antwort ist eine Antwort auf die von illissius Punkt für Punkt aufgeworfenen Fragen:

  • Es ist hässlich zu benutzen. $ (fooBar '' Asdf) sieht einfach nicht gut aus. Oberflächlich, sicher, aber es trägt dazu bei.

Genau. Ich habe das Gefühl, dass $ () so ausgewählt wurde, dass es Teil der Sprache ist - unter Verwendung der bekannten Symbolpalette von Haskell. Genau das möchten Sie jedoch nicht in den Symbolen, die für das Makrospleißen verwendet werden. Sie passen definitiv zu viel zusammen, und dieser kosmetische Aspekt ist ziemlich wichtig. Ich mag das Aussehen von {{}} für Spleiße, weil sie optisch sehr unterschiedlich sind.

  • Es ist noch hässlicher zu schreiben. Das Zitieren funktioniert manchmal, aber die meiste Zeit müssen Sie manuell AST-Transplantationen und -Installationen durchführen. Die [API] [1] ist groß und unhandlich, es gibt immer viele Fälle, die Sie nicht interessieren, aber dennoch versenden müssen, und die Fälle, die Sie interessieren, sind in der Regel in mehreren ähnlichen, aber nicht identischen Formen (Daten) vorhanden vs. Newtype, Record-Style vs. normale Konstruktoren usw.). Es ist langweilig und sich wiederholend zu schreiben und kompliziert genug, um nicht mechanisch zu sein. Der [Reformvorschlag] [2] befasst sich mit einigen dieser Fragen (wodurch Zitate allgemeiner anwendbar werden).

Ich stimme dem jedoch auch zu, wie einige der Kommentare in "New Directions for TH" feststellen, ist das Fehlen einer guten sofort einsatzbereiten AST-Zitierung kein kritischer Fehler. In diesem WIP-Paket möchte ich diese Probleme in Bibliotheksform beheben: https://github.com/mgsloan/quasi-extras . Bisher erlaube ich das Spleißen an einigen Stellen mehr als gewöhnlich und kann Muster auf ASTs abgleichen.

  • Die Bühnenbeschränkung ist die Hölle. Die Möglichkeit, Funktionen, die im selben Modul definiert sind, nicht zu verbinden, ist der kleinere Teil davon. Die andere Konsequenz ist, dass, wenn Sie einen Spleiß der obersten Ebene haben, alles, was danach im Modul vorhanden ist, für alles, was davor liegt, außer Reichweite ist. Andere Sprachen mit dieser Eigenschaft (C, C ++) machen es funktionsfähig, indem Sie deklarierte Dinge weiterleiten können, Haskell jedoch nicht. Wenn Sie zyklische Verweise zwischen gespleißten Deklarationen oder deren Abhängigkeiten und Abhängigkeiten benötigen, werden Sie normalerweise nur geschraubt.

Ich bin auf das Problem gestoßen, dass zyklische TH-Definitionen vorher unmöglich waren ... Es ist ziemlich ärgerlich. Es gibt eine Lösung, aber sie ist hässlich - packen Sie die an der zyklischen Abhängigkeit beteiligten Dinge in einen TH-Ausdruck ein, der alle generierten Deklarationen kombiniert. Einer dieser Deklarationsgeneratoren könnte nur ein Quasi-Quoter sein, der Haskell-Code akzeptiert.

  • Es ist prinzipienlos. Damit meine ich, dass die meiste Zeit, wenn Sie eine Abstraktion ausdrücken, hinter dieser Abstraktion ein Prinzip oder ein Konzept steckt. Für viele Abstraktionen kann das Prinzip dahinter in ihren Typen ausgedrückt werden. Wenn Sie eine Typklasse definieren, können Sie häufig Gesetze formulieren, denen Instanzen gehorchen sollten und die Clients annehmen können. Wenn Sie die [neue Generika-Funktion] von GHC [3] verwenden, um die Form einer Instanzdeklaration über einen beliebigen Datentyp (innerhalb von Grenzen) zu abstrahieren, können Sie sagen: "Für Summentypen funktioniert das so, für Produkttypen funktioniert es so." ". Aber Template Haskell ist nur dumme Makros. Es ist keine Abstraktion auf der Ebene der Ideen, sondern eine Abstraktion auf der Ebene der ASTs, die besser, aber nur bescheiden ist, als die Abstraktion auf der Ebene des Klartextes.

Es ist nur prinzipienlos, wenn Sie prinzipienlose Dinge damit machen. Der einzige Unterschied besteht darin, dass Sie mit den vom Compiler implementierten Abstraktionsmechanismen mehr Vertrauen haben, dass die Abstraktion nicht undicht ist. Vielleicht klingt die Demokratisierung des Sprachdesigns ein bisschen beängstigend! Ersteller von TH-Bibliotheken müssen die Bedeutung und die Ergebnisse der von ihnen bereitgestellten Tools gut dokumentieren und klar definieren. Ein gutes Beispiel für prinzipielles TH ist das Ableitungspaket: http://hackage.haskell.org/package/derive - es verwendet eine DSL, so dass das Beispiel vieler Ableitungen / spezifiziert / die tatsächliche Ableitung.

  • Es bindet Sie an GHC. Theoretisch könnte es ein anderer Compiler implementieren, aber in der Praxis bezweifle ich, dass dies jemals passieren wird. (Dies steht im Gegensatz zu verschiedenen Typ-Systemerweiterungen, die, obwohl sie derzeit möglicherweise nur von GHC implementiert werden, leicht vorstellbar sind, später von anderen Compilern übernommen und schließlich standardisiert zu werden.)

Das ist ein ziemlich guter Punkt - die TH-API ist ziemlich groß und klobig. Eine Neuimplementierung scheint schwierig zu sein. Es gibt jedoch nur wenige Möglichkeiten, das Problem der Darstellung von Haskell-ASTs zu lösen. Ich stelle mir vor, dass Sie durch Kopieren der TH-ADTs und Schreiben eines Konverters in die interne AST-Darstellung einen guten Weg dorthin finden. Dies wäre gleichbedeutend mit dem (nicht unbedeutenden) Aufwand, haskell-src-meta zu erstellen. Es könnte auch einfach neu implementiert werden, indem der TH AST hübsch gedruckt und der interne Parser des Compilers verwendet wird.

Obwohl ich mich irren könnte, sehe ich TH aus Sicht der Implementierung nicht als so kompliziert wie eine Compiler-Erweiterung an. Dies ist tatsächlich einer der Vorteile des "Einfachhaltens" und des Fehlens der Grundschicht als theoretisch ansprechendes, statisch überprüfbares Schablonensystem.

  • Die API ist nicht stabil. Wenn GHC neue Sprachfunktionen hinzugefügt und das Template-Haskell-Paket aktualisiert wird, um diese zu unterstützen, sind häufig abwärtsinkompatible Änderungen an den TH-Datentypen erforderlich. Wenn Sie möchten, dass Ihr TH-Code mit mehr als nur einer Version von GHC kompatibel ist, müssen Sie sehr vorsichtig sein und möglicherweise verwenden CPP.

Dies ist auch ein guter Punkt, aber etwas dramatisch. Zwar gab es in letzter Zeit API-Ergänzungen, diese haben jedoch nicht in großem Umfang zu Brüchen geführt. Ich denke auch, dass mit dem überlegenen AST-Zitat, das ich zuvor erwähnt habe, die API, die tatsächlich verwendet werden muss, sehr stark reduziert werden kann. Wenn keine Konstruktion / Übereinstimmung unterschiedliche Funktionen benötigt und stattdessen als Literale ausgedrückt wird, verschwindet der größte Teil der API. Darüber hinaus lässt sich der von Ihnen geschriebene Code leichter auf AST-Darstellungen für Sprachen portieren, die Haskell ähnlich sind.


Zusammenfassend denke ich, dass TH ein mächtiges, halb vernachlässigtes Werkzeug ist. Weniger Hass könnte zu einem lebendigeren Ökosystem von Bibliotheken führen und die Implementierung von Prototypen mit mehr Sprachmerkmalen fördern. Es wurde beobachtet, dass TH ein übermächtiges Werkzeug ist, mit dem Sie fast alles tun können. Anarchie! Nun, ich bin der Meinung, dass Sie mit dieser Fähigkeit die meisten ihrer Einschränkungen überwinden und Systeme konstruieren können, die zu prinzipiellen Metaprogrammierungsansätzen fähig sind. Es lohnt sich, hässliche Hacks zu verwenden, um die "richtige" Implementierung zu simulieren, da auf diese Weise das Design der "richtigen" Implementierung allmählich klar wird.

In meiner persönlichen idealen Version des Nirvana würde ein Großteil der Sprache tatsächlich aus dem Compiler in Bibliotheken dieser Art verschoben. Die Tatsache, dass die Funktionen als Bibliotheken implementiert sind, hat keinen großen Einfluss auf ihre Fähigkeit, originalgetreu zu abstrahieren.

Was ist die typische Antwort von Haskell auf Boilerplate-Code? Abstraktion. Was sind unsere Lieblingsabstraktionen? Funktionen und Typenklassen!

Mit Typklassen können wir eine Reihe von Methoden definieren, die dann in allen Arten von Funktionen verwendet werden können, die für diese Klasse generisch sind. Abgesehen davon können Klassen Boilerplate nur vermeiden, indem sie "Standarddefinitionen" anbieten. Hier ist ein Beispiel für eine prinzipienlose Funktion!

  • Minimale Bindungssätze sind nicht deklarierbar / vom Compiler überprüfbar. Dies könnte zu unbeabsichtigten Definitionen führen, die aufgrund gegenseitiger Rekursion den Boden ergeben.

  • Trotz der großen Bequemlichkeit und Leistung dieser ergeben würde, können Sie keine übergeordnete Klasse Standardwerte angeben, aufgrund orphan Instanzen http://lukepalmer.wordpress.com/2009/01/25/a-world-without-orphans/ Diese würden wir uns das beheben numerische Hierarchie anmutig!

  • Die Suche nach TH-ähnlichen Funktionen für Methodenstandards führte zu http://www.haskell.org/haskellwiki/GHC.Generics . Obwohl dies cool ist, war meine einzige Erfahrung mit dem Debuggen von Code mit diesen Generika nahezu unmöglich, da der Typ für ADT induziert wurde und ADT so kompliziert wie ein AST ist. https://github.com/mgsloan/th-extra/commit/d7784d95d396eb3abdb409a24360beb03731c88c

    Mit anderen Worten, dies ging nach den von TH bereitgestellten Merkmalen, aber es musste eine ganze Domäne der Sprache, die Konstruktionssprache, in eine Typensystemdarstellung heben. Ich kann zwar sehen, dass es für Ihr häufig auftretendes Problem gut funktioniert, aber für komplexe Probleme scheint es anfällig zu sein, einen Stapel von Symbolen zu liefern, der weitaus schrecklicher ist als TH-Hackery.

    Mit TH können Sie den Ausgabecode zur Kompilierungszeit auf Wertebene berechnen, während Generics Sie dazu zwingt, den Mustervergleichs- / Rekursionsteil des Codes in das Typsystem zu heben. Dies schränkt den Benutzer zwar auf einige ziemlich nützliche Arten ein, aber ich denke nicht, dass sich die Komplexität lohnt.

Ich denke, dass die Ablehnung von TH und lisp-artiger Metaprogrammierung dazu geführt hat, dass Dinge wie Methodenvorgaben anstelle einer flexibleren Makro-Erweiterung wie Instanzdeklarationen bevorzugt wurden. Die Disziplin, Dinge zu vermeiden, die zu unvorhergesehenen Ergebnissen führen könnten, ist klug. Wir sollten jedoch nicht ignorieren, dass das leistungsfähige Typsystem von Haskell eine zuverlässigere Metaprogrammierung ermöglicht als in vielen anderen Umgebungen (durch Überprüfen des generierten Codes).


4
Diese Antwort steht nicht für sich allein: Sie verweisen auf eine andere Antwort, die ich suchen muss, bevor ich Ihre richtig lesen kann.
Ben Millwood

Es ist wahr. Ich versuchte klar zu machen, worüber trotzdem gesprochen wurde. Vielleicht bearbeite ich die Punkte von illisuis.
mgsloan

Ich sollte sagen, dass "prinzipienlos" vielleicht ein stärkeres Wort ist, als ich hätte verwenden sollen, mit einigen Konnotationen, die ich nicht beabsichtigt hatte - es ist sicherlich nicht skrupellos! Dieser Aufzählungspunkt war derjenige, mit dem ich am meisten Probleme hatte, weil ich dieses Gefühl oder diese ungeformte Idee im Kopf habe und Schwierigkeiten habe, sie in Worte zu fassen, und "prinzipienlos" war eines der Wörter, die ich begriff und die sich irgendwo in der Nähe befanden. "Diszipliniert" ist wahrscheinlich näher. Da ich keine klare und prägnante Formulierung hatte, ging ich stattdessen auf einige Beispiele ein. Wenn mir jemand klarer erklären kann, was ich wahrscheinlich gemeint habe, würde ich es begrüßen!
Glaebhoerl

(Ich denke auch, dass Sie möglicherweise die Staging-Einschränkung und die hässlich zu schreibenden Zitate
geändert haben

1
Doh! Vielen Dank für den Hinweis! Fest. Ja, undiszipliniert wäre wahrscheinlich eine bessere Art, es auszudrücken. Ich denke, dass hier wirklich zwischen "eingebauten" und daher "gut verstandenen" Abstraktionsmechanismen und "Ad-hoc" - oder benutzerdefinierten Abstraktionsmechanismen unterschieden wird. Ich nehme an, ich meine, dass Sie möglicherweise eine TH-Bibliothek definieren könnten, die etwas Ähnliches wie den Versand von
Typklassen

8

Ein eher pragmatisches Problem bei Template Haskell ist, dass es nur funktioniert, wenn der Bytecode-Interpreter von GHC verfügbar ist, was nicht bei allen Architekturen der Fall ist. Wenn Ihr Programm Template Haskell verwendet oder auf Bibliotheken angewiesen ist, die es verwenden, wird es nicht auf Computern mit einer ARM-, MIPS-, S390- oder PowerPC-CPU ausgeführt.

Dies ist in der Praxis relevant: git-annex ist ein in Haskell geschriebenes Tool, das sinnvoll ist, um auf Computern ausgeführt zu werden, die sich um Speicher sorgen. Solche Computer verfügen häufig über Nicht-i386-CPUs. Persönlich führe ich git-annex auf einer NSLU 2 aus (32 MB RAM, 266 MHz CPU; wussten Sie, dass Haskell auf solcher Hardware gut funktioniert?). Wenn Template Haskell verwendet wird, ist dies nicht möglich.

(Die Situation über GHC auf ARM verbessert sich heutzutage sehr und ich denke, 7.4.2 funktioniert sogar, aber der Punkt bleibt bestehen).


1
"Template Haskell verlässt sich auf den integrierten Bytecode-Compiler und -Interpreter von GHC, um die Spleißausdrücke auszuführen." - haskell.org/ghc/docs/7.6.2/html/users_guide/…
Joachim Breitner

2
ah, ich das - nein, TH wird ohne den Bytecode-Interpreter nicht funktionieren, aber das unterscheidet sich von (obwohl relevant für) ghci. Es würde mich nicht wundern, wenn es eine perfekte Beziehung zwischen der Verfügbarkeit von ghci und der Verfügbarkeit des Bytecode-Interpreters gäbe, da ghci vom Bytecode-Interpreter abhängt, aber das Problem dort ist das Fehlen des Bytecode-Interpreters, nicht das Fehlen von ghci speziell.
Muhmuhten

1
(Übrigens hat Ghci eine amüsant schlechte Unterstützung für die interaktive Nutzung von TH. Versuch ghci -XTemplateHaskell <<< '$(do Language.Haskell.TH.runIO $ (System.Random.randomIO :: IO Int) >>= print; [| 1 |] )')
Muhmuhten

Ok, mit ghci bezog ich mich auf die Fähigkeit von GHC, Code zu interpretieren (anstatt zu kompilieren), unabhängig davon, ob der Code interaktiv in der ghci-Binärdatei oder von TH splice kommt.
Joachim Breitner

6

Warum ist TH schlecht? Für mich kommt es darauf an:

Wenn Sie so viel sich wiederholenden Code produzieren müssen, dass Sie versuchen, TH zum automatischen Generieren zu verwenden, machen Sie es falsch!

Denk darüber nach. Der halbe Reiz von Haskell besteht darin, dass Sie durch sein hochwertiges Design große Mengen an nutzlosem Boilerplate-Code vermeiden können, den Sie in anderen Sprachen schreiben müssen. Wenn Sie eine Codegenerierung zur Kompilierungszeit benötigen , sagen Sie im Grunde, dass entweder Ihre Sprache oder Ihr Anwendungsdesign versagt hat. Und wir Programmierer scheitern nicht gern.

Manchmal ist es natürlich notwendig. Aber manchmal können Sie vermeiden, TH zu benötigen, indem Sie mit Ihren Designs nur ein bisschen schlauer umgehen.

(Die andere Sache ist, dass TH ziemlich niedrig ist. Es gibt kein großartiges Design auf hoher Ebene; viele interne Implementierungsdetails von GHC werden offengelegt. Und das macht die API anfällig für Änderungen ...)


Ich denke nicht, dass dies bedeutet, dass Ihre Sprache oder Anwendung Sie gescheitert ist, insbesondere wenn wir QuasiQuotes berücksichtigen. Bei der Syntax gibt es immer Kompromisse. Einige Syntaxen beschreiben eine bestimmte Domäne besser, sodass Sie manchmal zu einer anderen Syntax wechseln möchten. Mit QuasiQuotes können Sie elegant zwischen den Syntaxen wechseln. Dies ist sehr leistungsfähig und wird von Jessod und anderen Apps verwendet. Es ist eine erstaunliche Funktion, HTML-Generierungscode mit einer Syntax schreiben zu können, die sich wie HTML anfühlt.
CoolCodeBro

@CoolCodeBro Ja, Quasi-Quoting ist ganz nett, und ich nehme an, dass es etwas orthogonal zu TH ist. (Offensichtlich ist es zusätzlich zu TH implementiert.) Ich habe mehr darüber nachgedacht, TH zu verwenden, um Klasseninstanzen zu generieren oder Funktionen mehrerer Aritäten zu erstellen, oder so ähnlich.
MathematicalOrchid
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.