Suche…


Bemerkungen

Was ist Template Haskell?

Template Haskell bezieht sich auf die in GHC Haskell integrierten Template-Metaprogrammierungsfunktionen. Das Papier der ursprüngliche Implementierung der Beschreibung gefunden werden kann hier .

Was sind Stufen? (Oder was ist die Bühneneinschränkung?)

Stufen beziehen sie auf , wenn der Code ausgeführt wird. Normalerweise wird Code nur zur Laufzeit ausgeführt, aber mit Template Haskell kann Code zur Kompilierzeit ausgeführt werden. "Normal" -Code ist Stufe 0 und der Kompilierzeitcode ist Stufe 1.

Die Einschränkung der Stufe bezieht sich auf die Tatsache, dass ein Stufe 0-Programm möglicherweise nicht auf Stufe 1 ausgeführt werden kann. Dies wäre gleichbedeutend mit der Möglichkeit, ein normales Programm (nicht nur ein Meta-Programm) zur Kompilierzeit auszuführen.

Konventionell (und der Einfachheit der Implementierung halber) ist Code im aktuellen Modul immer Stufe 0 und Code, der von allen anderen Modulen importiert wird, Stufe 1. Aus diesem Grund dürfen nur Ausdrücke aus anderen Modulen gespleißt werden.

Beachten Sie, dass ein Programm der Stufe 1 ein Ausdruck der Stufe 0 des Typs Q Exp , Q Type usw. ist. aber das Gegenteil ist nicht wahr - nicht jeder Wert (Programm der Stufe 0) vom Typ Q Exp ist ein Programm der Stufe 1,

Da Spleiße verschachtelt werden können, können Identifizierer außerdem Stufen haben, die größer als 1 sind. Die Stufenbeschränkung kann dann verallgemeinert werden - ein Programm der Stufe n kann in keiner Stufe m> n ausgeführt werden . Beispielsweise können in bestimmten Fehlermeldungen Verweise auf solche Stufen größer als 1 angezeigt werden:

>:t [| \x -> $x |]

<interactive>:1:10: error:
    * Stage error: `x' is bound at stage 2 but used at stage 1
    * In the untyped splice: $x
      In the Template Haskell quotation [| \ x -> $x |]

Die Verwendung von Template Haskell führt zu Fehlern außerhalb des Gültigkeitsbereichs von nicht verknüpften Bezeichnern.

Normalerweise können alle Deklarationen in einem einzigen Haskell-Modul als gegenseitig rekursiv betrachtet werden. Mit anderen Worten, jede Deklaration auf oberster Ebene ist in einem einzigen Modul im Umfang jeder anderen. Wenn Template Haskell aktiviert ist, ändern sich die Bereichsregeln. Stattdessen wird das Modul in durch TH-Spleiße getrennte Codegruppen aufgeteilt, und jede Gruppe ist wechselseitig rekursiv. Jede Gruppe befindet sich im Bereich aller weiteren Gruppen.

Der Q-Typ

Der in Language.Haskell.TH.Syntax definierte Konstruktor Q :: * -> * stellt einen abstrakten Typ dar, der Berechnungen darstellt, die Zugriff auf die Kompilierzeitumgebung des Moduls haben, in dem die Berechnung ausgeführt wird. Der Q Typ behandelt auch die variable Substitution, die als Name Capture durch TH bezeichnet wird (und hier erläutert wird). Alle Spleiße haben für einige X Typ QX .

Die Umgebung zur Kompilierungszeit umfasst:

  • Kennungen und Informationen zu diesen Kennungen,
    • Arten von Funktionen
    • Typen und Quelldatentypen von Konstruktoren
    • vollständige Spezifikation der Typdeklarationen (Klassen, Typfamilien)
  • Die Position im Quellcode (Zeile, Spalte, Modul, Paket), an der der Splice erfolgt
  • Fixitäten von Funktionen (GHC 7.10)
  • aktivierte GHC-Erweiterungen (GHC 8.0)

Der Q Typ kann auch neue Namen mit der Funktion newName :: String -> Q Name generieren. Beachten Sie, dass der Name nicht irgendwo implizit gebunden ist. Daher muss der Benutzer ihn selbst binden. Daher muss der Benutzer dafür verantwortlich sein, dass der Name gut verwendet wird.

Q hat Instanzen für Functor,Monad,Applicative Dies ist die Hauptschnittstelle für die Bearbeitung von Q Werten, zusammen mit den in Language.Haskell.TH.Lib bereitgestellten Kombinatoren, die für jeden Konstruktor des TH ast des Formulars eine Hilfsfunktion definieren:

LitE :: Lit -> Exp
litE :: Lit -> ExpQ

AppE :: Exp -> Exp -> Exp 
appE :: ExpQ -> ExpQ -> ExpQ

Beachten Sie, dass ExpQ , TypeQ , DecsQ und PatQ Synonyme für die AST-Typen sind, die normalerweise im Q Typ gespeichert werden.

Die TH-Bibliothek stellt eine Funktion runQ :: Quasi m => Q a -> ma , und es gibt eine Instanz Quasi IO , es scheint also, dass der Q Typ nur eine schicke IO . Jedoch ist die Verwendung von runQ :: Q a -> IO a eine produziert IO Aktion , die keinen Zugang zu Kompilierung-Umgebung hat - dies ist nur in dem tatsächlich verfügbaren Q - Typ. Solche IO Aktionen schlagen zur Laufzeit fehl, wenn versucht wird, auf die Umgebung zuzugreifen.

Ein n-arity Curry

Das vertraute

curry :: ((a,b) -> c) -> a -> b -> c
curry = \f a b -> f (a,b)

Funktion kann auf Tupel beliebiger Arität verallgemeinert werden, zum Beispiel:

curry3 :: ((a, b, c) -> d) -> a -> b -> c -> d
curry4 :: ((a, b, c, d) -> e) -> a -> b -> c -> d -> e 

Das Schreiben solcher Funktionen für Tupel der Arity 2 bis (zB) 20 von Hand wäre jedoch langwierig (und die Tatsache zu ignorieren, dass das Vorhandensein von 20 Tupeln in Ihrem Programm fast sicher Designprobleme signalisiert, die mit Datensätzen behoben werden sollten).

Wir können Template Haskell verwenden, um solche curryN Funktionen für beliebige n zu erzeugen:

{-# LANGUAGE TemplateHaskell #-}
import Control.Monad (replicateM) 
import Language.Haskell.TH (ExpQ, newName, Exp(..), Pat(..))
import Numeric.Natural (Natural) 

curryN :: Natural -> Q Exp

Die curryN Funktion nimmt eine natürliche Zahl an und erzeugt die Curry-Funktion dieser Arität als Haskell-AST.

curryN n = do
  f  <- newName "f"
  xs <- replicateM (fromIntegral n) (newName "x")

Zuerst erzeugen wir frische Typvariablen für jedes der Argumente der Funktion - eine für die Eingabefunktion und eine für jedes der Argumente für diese Funktion.

  let args = map VarP (f:xs)

Der Ausdruck args repräsentiert das Muster f x1 x2 .. xn . Beachten Sie, dass ein Muster eine separate syntaktische Entität ist - wir könnten dasselbe Muster in ein Lambda oder eine Funktionsbindung oder sogar die LHS einer Let-Bindung (die ein Fehler wäre) platzieren.

      ntup = TupE (map VarE xs)

Die Funktion muss das Argument-Tupel aus der Folge von Argumenten erstellen, wie wir es hier gemacht haben. Beachten Sie den Unterschied zwischen Mustervariablen ( VarP ) und Ausdrucksvariablen ( VarE ).

  return $ LamE args (AppE (VarE f) ntup)

Schließlich ist der Wert, den wir erzeugen, AST \f x1 x2 .. xn -> f (x1, x2, .. , xn) .

Wir hätten diese Funktion auch mit Zitaten und "angehobenen" Konstruktoren schreiben können:

...
import Language.Haskell.TH.Lib  

curryN' :: Natural -> ExpQ
curryN' n = do
  f  <- newName "f"
  xs <- replicateM (fromIntegral n) (newName "x")
  lamE (map varP (f:xs)) 
        [| $(varE f) $(tupE (map varE xs)) |]

Beachten Sie, dass Zitate syntaktisch gültig sein müssen, also [| \ $(map varP (f:xs)) -> .. |] ist ungültig, da es im regulären Haskell nicht möglich ist, eine 'Liste' von Mustern zu deklarieren - das obige wird als \ var -> .. und das interpretiert Es wird erwartet, dass der gespleißte Ausdruck den Typ PatQ , dh ein einzelnes Muster, keine Liste von Mustern.

Zum Schluss können wir diese TH-Funktion in GHCi laden:

>:set -XTemplateHaskell
>:t $(curryN 5)
$(curryN 5)
  :: ((t1, t2, t3, t4, t5) -> t) -> t1 -> t2 -> t3 -> t4 -> t5 -> t
>$(curryN 5) (\(a,b,c,d,e) -> a+b+c+d+e) 1 2 3 4 5
15

Dieses Beispiel wird hauptsächlich von hier aus angepasst.

Syntax von Template Haskell und Quasiquotes

Template Haskell wird von der GHC-Erweiterung -XTemplateHaskell . Diese Erweiterung ermöglicht alle in diesem Abschnitt ausführlicher beschriebenen syntaktischen Funktionen. Die vollständigen Details zu Template Haskell finden Sie in der Bedienungsanleitung .

Spleiße

  • Ein Splice ist eine neue syntaktische Entität, die von Template Haskell aktiviert wird und als $(...) , wobei (...) ein Ausdruck ist.

  • Zwischen $ und dem ersten Zeichen des Ausdrucks darf kein Leerzeichen stehen. und Template Haskell überschreibt die Analyse des $ -Operators - z. B. wird f$g normalerweise als ($) fg analysiert, wohingegen bei aktiviertem Template Haskell ein Splice analysiert wird.

  • Wenn auf der obersten Ebene ein Spleiß erscheint, kann das $ weggelassen werden. In diesem Fall ist der gespleißte Ausdruck die gesamte Zeile.

  • Ein Splice stellt Code dar, der zur Kompilierzeit ausgeführt wird, um einen Haskell-AST zu erzeugen, und dieser AST wird als Haskell-Code kompiliert und in das Programm eingefügt

  • Spleiße können anstelle von Ausdrücken, Mustern, Typen und Deklarationen der obersten Ebene angezeigt werden. Der Typ des gespleißten Ausdrucks ist jeweils Q Exp , Q Pat , Q Type , Q [Decl] . Beachten Sie, dass Deklarationsspleiße möglicherweise nur auf der obersten Ebene angezeigt werden, während sich die anderen in anderen Ausdrücken, Mustern oder Typen befinden können.

Ausdruckszitate (Hinweis: kein QuasiQuotation)

  • Ein Ausdruckszitat ist eine neue syntaktische Entität, geschrieben als eines der folgenden Elemente:

    • [e|..|] oder [|..|] - .. ist ein Ausdruck und das Zitat hat den Typ Q Exp ;
    • [p|..|] - .. ist ein Muster und das Zitat hat den Typ Q Pat ;
    • [t|..|] - .. ist ein Typ und das Angebot hat den Typ Q Type ;
    • [d|..|] - .. ist eine Liste von Deklarationen und das Zitat hat den Typ Q [Dec] .
  • Ein Ausdruckszitat benötigt ein Kompilierzeitprogramm und erstellt die von diesem Programm dargestellte AST.

  • Die Verwendung eines Werts in einem Zitat (z. B. \x -> [| x |] ) ohne Spleiß entspricht dem syntaktischen Zucker für \x -> [| $(lift x) |] , wobei lift :: Lift t => t -> Q Exp von der Klasse kommt

    class Lift t where
      lift :: t -> Q Exp
      default lift :: Data t => t -> Q Exp

Typisierte Spleiße und Zitate

  • Typisierte Spleiße ähneln den zuvor erwähnten (nicht typisierten) Spleißen und werden als $$(..) wobei (..) ein Ausdruck ist.

  • Wenn e den Typ Q (TExp a) hat, hat $$e den Typ a .

  • Typisierte Zitate haben die Form [||..||] wobei .. ein Ausdruck vom Typ a ; Das resultierende Zitat hat den Typ Q (TExp a) .

  • Typisierte Ausdrücke können in nicht typisierte umgewandelt werden: unType :: TExp a -> Exp .

QuasiQuotes

  • QuasiQuotes verallgemeinern Ausdruckszitate - der Parser, der im Ausdruckszitat verwendet wird, ist früher einer festen Menge ( e,p,t,d ), aber QuasiQuotes ermöglicht die Definition und die Erstellung eines benutzerdefinierten Parsers zur Kompilierzeit. Quasi-Zitate können im selben Kontext wie reguläre Zitate erscheinen.

  • Ein Quasi-Zitat wird als [iden|...|] , wobei iden eine Kennung vom Typ Language.Haskell.TH.Quote.QuasiQuoter .

  • Ein QuasiQuoter besteht einfach aus vier Parsern, einen für jeden der verschiedenen Kontexte, in denen Zitate auftreten können:

    data QuasiQuoter = QuasiQuoter { quoteExp  :: String -> Q Exp,
                                     quotePat  :: String -> Q Pat,
                                     quoteType :: String -> Q Type,
                                     quoteDec  :: String -> Q [Dec] }

Namen

  • Haskell-Bezeichner werden durch den Typ Language.Haskell.TH.Syntax.Name . Namen bilden die Blätter abstrakter Syntaxbäume, die Haskell-Programme in Template Haskell darstellen.

  • Eine Kennung, die sich aktuell im Gültigkeitsbereich befindet, kann in einen Namen umgewandelt werden mit: 'e oder 'T Im ersten Fall wird e im Ausdrucksbereich interpretiert, während im zweiten Fall T im Typenbereich liegt (unter Hinweis darauf, dass Typen und Wertkonstruktoren den Namen ohne Übereinstimmung in Haskell teilen können).



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow