Haskell Language
Sjabloon Haskell & QuasiQuotes
Zoeken…
Opmerkingen
Wat is Template Haskell?
Template Haskell verwijst naar de meta-programmeerfaciliteiten voor sjablonen die in GHC Haskell zijn ingebouwd. De papieren beschrijving van de oorspronkelijke uitvoering kan worden gevonden hier .
Wat zijn fasen? (Of, wat is de fasebeperking?)
Stadia verwijzen naar wanneer code wordt uitgevoerd. Normaal gesproken wordt code alleen tijdens runtime uitgevoerd, maar met Template Haskell kan code tijdens het compileren worden uitgevoerd. "Normale" code is fase 0 en compilatie-tijdcode is fase 1.
De fasebeperking verwijst naar het feit dat een fase 0-programma mogelijk niet wordt uitgevoerd in fase 1 - dit zou hetzelfde zijn als het kunnen uitvoeren van een normaal programma (niet alleen metaprogramma) tijdens het compileren.
Volgens conventie (en omwille van de eenvoud van de implementatie) is code in de huidige module altijd fase 0 en is code geïmporteerd uit alle andere modules fase 1. Om deze reden kunnen alleen expressies van andere modules worden gesplitst.
Merk op dat een fase 1-programma een fase 0-expressie is van het type Q Exp
, Q Type
, enz .; maar het omgekeerde is niet waar - niet elke waarde (fase 0-programma) van type Q Exp
is een fase 1-programma,
Aangezien splices kunnen worden genest, kunnen identificators bovendien fasen groter dan 1 hebben. De fasebeperking kan vervolgens worden gegeneraliseerd - een fase n- programma kan in geen enkele fase m> n worden uitgevoerd. In bepaalde foutmeldingen ziet u bijvoorbeeld verwijzingen naar dergelijke fasen groter dan 1:
>: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 |]
Gebruikt het gebruik van Template Haskell niet-in-scope fouten door niet-gerelateerde identifiers?
Normaal gesproken kunnen alle declaraties in een enkele Haskell-module worden beschouwd als zijnde allemaal wederzijds recursief. Met andere woorden, elke aangifte op het hoogste niveau valt binnen het bereik van elkaar in één enkele module. Wanneer Template Haskell is ingeschakeld, veranderen de scopingregels - de module wordt in plaats daarvan opgedeeld in groepen code gescheiden door TH-splitsingen, en elke groep is wederzijds recursief en elke groep valt binnen het bereik van alle verdere groepen.
Het Q-type
De constructor van het type Q :: * -> *
gedefinieerd in Language.Haskell.TH.Syntax
is een abstract type dat berekeningen weergeeft die toegang hebben tot de compilatieomgeving van de module waarin de berekening wordt uitgevoerd. Het Q
type verwerkt ook variabele substituion, genaamd name capture by TH (en wordt hier besproken.) Alle splices hebben type QX
voor sommige X
De compilatieomgeving omvat:
- identificatiegegevens binnen het bereik en informatie over deze identificatiegegevens,
- soorten functies
- typen en brongegevenstypen van constructeurs
- volledige specificatie van typeaangiften (klassen, typefamilies)
- de locatie in de broncode (regel, kolom, module, pakket) waar de splitsing voorkomt
- fixiteiten van functies (GHC 7.10)
- ingeschakelde GHC-extensies (GHC 8.0)
Het Q
type heeft ook de mogelijkheid om nieuwe namen te genereren, met de functie newName :: String -> Q Name
. Merk op dat de naam nergens impliciet is gebonden, dus de gebruiker moet deze zelf binden, en het is dus de verantwoordelijkheid van de gebruiker om ervoor te zorgen dat het resulterende gebruik van de naam goed is.
Q
heeft instanties voor Functor,Monad,Applicative
en dit is de hoofdinterface voor het manipuleren van Q
waarden, samen met de combinators in Language.Haskell.TH.Lib
, die een helpfunctie definiëren voor elke constructor van de TH ast van de vorm:
LitE :: Lit -> Exp
litE :: Lit -> ExpQ
AppE :: Exp -> Exp -> Exp
appE :: ExpQ -> ExpQ -> ExpQ
Merk op dat ExpQ
, TypeQ
, DecsQ
en PatQ
synoniemen zijn voor de AST-typen die doorgaans in het Q
type worden opgeslagen.
De TH-bibliotheek biedt een functie runQ :: Quasi m => Q a -> ma
, en er is een instantie Quasi IO
, dus het lijkt erop dat het Q
type gewoon een fancy IO
. Echter, het gebruik van runQ :: Q a -> IO a
produceert een IO
handeling die geen toegang tot de compile-time omgeving heeft - dit is alleen beschikbaar in de werkelijke Q
type. Dergelijke IO
acties mislukken tijdens runtime als ze proberen toegang te krijgen tot de genoemde omgeving.
Een n-arity curry
Het vertrouwde
curry :: ((a,b) -> c) -> a -> b -> c
curry = \f a b -> f (a,b)
functie kan worden gegeneraliseerd tot tupels van willekeurige arity, bijvoorbeeld:
curry3 :: ((a, b, c) -> d) -> a -> b -> c -> d
curry4 :: ((a, b, c, d) -> e) -> a -> b -> c -> d -> e
Het handmatig schrijven van dergelijke functies voor tupels van arity 2 tot (bijv.) 20 zou echter vervelend zijn (en het feit negeren dat de aanwezigheid van 20 tuples in uw programma vrijwel zeker ontwerpproblemen signaleert die met records moeten worden opgelost).
We kunnen Template Haskell gebruiken om dergelijke curryN
functies voor willekeurige n
te produceren:
{-# LANGUAGE TemplateHaskell #-}
import Control.Monad (replicateM)
import Language.Haskell.TH (ExpQ, newName, Exp(..), Pat(..))
import Numeric.Natural (Natural)
curryN :: Natural -> Q Exp
De curryN
functie heeft een natuurlijk getal en produceert de curry-functie van die arity, als een Haskell AST.
curryN n = do
f <- newName "f"
xs <- replicateM (fromIntegral n) (newName "x")
Eerst produceren we nieuwe typevariabelen voor elk van de argumenten van de functie - een voor de invoerfunctie en een voor elk van de argumenten voor de functie.
let args = map VarP (f:xs)
De uitdrukking args
vertegenwoordigt het patroon f x1 x2 .. xn
. Merk op dat een patroon een afzonderlijke syntactische entiteit is - we kunnen hetzelfde patroon nemen en het in een lambda plaatsen, of een functiebinding, of zelfs de LHS van een let-binding (wat een fout zou zijn).
ntup = TupE (map VarE xs)
De functie moet het argument tuple bouwen op basis van de reeks argumenten, wat we hier hebben gedaan. Let op het onderscheid tussen patroonvariabelen ( VarP
) en expressievariabelen ( VarE
).
return $ LamE args (AppE (VarE f) ntup)
Ten slotte is de waarde die we produceren de AST \f x1 x2 .. xn -> f (x1, x2, .. , xn)
.
We hadden deze functie ook kunnen schrijven met citaten en 'opgetilde' constructeurs:
...
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)) |]
Merk op dat offertes syntactisch geldig moeten zijn, dus [| \ $(map varP (f:xs)) -> .. |]
is ongeldig, omdat er in reguliere Haskell geen manier is om een 'lijst' van patronen aan te geven - het bovenstaande wordt geïnterpreteerd als \ var -> ..
en de van gesplitste expressie wordt verwacht dat het type PatQ
, dat wil zeggen een enkel patroon, geen lijst met patronen.
Eindelijk kunnen we deze TH-functie 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
Dit voorbeeld is voornamelijk vanaf hier aangepast.
Syntaxis van sjabloon Haskell en Quasiquotes
Template Haskell wordt ingeschakeld door de extensie -XTemplateHaskell
GHC. Deze extensie maakt alle syntactische functies mogelijk die verder in dit gedeelte worden beschreven. De volledige details over Template Haskell worden gegeven door de gebruikershandleiding .
splices
Een splice is een nieuwe syntactische entiteit mogelijk gemaakt door Template Haskell, geschreven als
$(...)
, waarbij(...)
een uitdrukking is.Er mag geen spatie staan tussen
$
en het eerste teken van de uitdrukking; en Template Haskell vervangt het parseren van de$
-operator - bijv.f$g
wordt normaal gesproken ontleed als($) fg
terwijl terwijl Template Haskell is ingeschakeld, het als een splitsing wordt ontleed.Wanneer een splitsing op het hoogste niveau verschijnt, kan de
$
worden weggelaten. In dit geval is de gesplitste uitdrukking de hele regel.Een splice vertegenwoordigt code die tijdens het compileren wordt uitgevoerd om een Haskell AST te produceren en die AST wordt gecompileerd als Haskell-code en in het programma wordt ingevoegd
Splices kunnen worden weergegeven in plaats van: expressies, patronen, typen en declaraties op het hoogste niveau. Het type van de gesplitste expressie, respectievelijk, is
Q Exp
,Q Pat
,Q Type
,Q [Decl]
. Houd er rekening mee dat declaratiesplitsingen alleen op het hoogste niveau kunnen worden weergegeven, terwijl de andere zich binnen andere expressies, patronen of typen bevinden.
Citaten van uitdrukkingen (opmerking: geen QuasiQuotation)
Een uitdrukking citaat is een nieuwe syntactische entiteit geschreven als een van:
-
[e|..|]
of[|..|]
-..
is een uitdrukking en de quote heeft typeQ Exp
; -
[p|..|]
-..
is een patroon en het citaat heeft typeQ Pat
; -
[t|..|]
-..
is een type en de offerte heeft typeQ Type
; -
[d|..|]
-..
is een lijst met declaraties en de offerte heeft typeQ [Dec]
.
-
Een uitdrukkingofferte neemt een compilatieprogramma en produceert de AST die wordt vertegenwoordigd door dat programma.
Het gebruik van een waarde in een offerte (bijv.
\x -> [| x |]
) zonder een splitsing komt overeen met syntactische suiker voor\x -> [| $(lift x) |]
, waarbijlift :: Lift t => t -> Q Exp
uit de klas komt
class Lift t where lift :: t -> Q Exp default lift :: Data t => t -> Q Exp
Getypte verbindingen en citaten
Getypte splices zijn vergelijkbaar met eerder genoemde (niet-getypeerde) splices en worden geschreven als
$$(..)
waarbij(..)
een uitdrukking is.Als
e
typeQ (TExp a)
dan heeft$$e
typea
.Getypte citaten hebben de vorm
[||..||]
waarbij..
een uitdrukking is van typea
; het resulterende citaat heeft typeQ (TExp a)
.Getypte expressie kan worden geconverteerd naar ongetypeerde:
unType :: TExp a -> Exp
.
QuasiQuotes
QuasiQuotes generaliseren expressiecitaten - voorheen was de parser die door de expressiecitaat wordt gebruikt, een van een vaste set (
e,p,t,d
), maar QuasiQuotes maakt het mogelijk om een aangepaste parser te definiëren en te gebruiken om tijdens het compileren code te produceren. Quasi-citaten kunnen in dezelfde context worden weergegeven als reguliere citaten.Een quasi-citaat wordt geschreven als
[iden|...|]
, waarbijiden
een id is van het typeLanguage.Haskell.TH.Quote.QuasiQuoter
.Een
QuasiQuoter
bestaat eenvoudig uit vier parsers, één voor elk van de verschillende contexten waarin citaten kunnen voorkomen:
data QuasiQuoter = QuasiQuoter { quoteExp :: String -> Q Exp, quotePat :: String -> Q Pat, quoteType :: String -> Q Type, quoteDec :: String -> Q [Dec] }
namen
Haskell-ID's worden weergegeven door het type
Language.Haskell.TH.Syntax.Name
. Namen vormen de bladeren van abstracte syntaxisbomen die Haskell-programma's in Template Haskell vertegenwoordigen.Een identificatie die momenteel binnen bereik is, kan worden omgezet in een naam met:
'e
of'T
In het eerste geval wordte
geïnterpreteerd in de uitdrukking scope, terwijl in het tweede gevalT
in de typescope is (eraan herinnerend dat types en waardeconstructors de naam zonder amiguiteit in Haskell kunnen delen).