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