Sök…


Anmärkningar

Vad är mall Haskell?

Mall Haskell hänvisar till mallens metaprogrammeringsanläggningar inbyggda i GHC Haskell. Uppsatsen som beskriver den ursprungliga implementeringen kan hittas här .

Vad är etapper? (Eller, vad är scenbegränsningen?)

Stegen avser när kod körs. Normalt förväntas kod endast vid körning, men med mall Haskell kan kod köras vid kompileringstid. "Normal" -kod är steg 0 och kompileringstidskod är steg 1.

Stegbegränsningen hänvisar till det faktum att ett steg 0-program inte kan köras i steg 1 - detta skulle motsvara att kunna köra något vanligt program (inte bara metaprogram) vid sammanställningstiden.

Genom konvention (och för enkelhetens implementering) är koden i den aktuella modulen alltid steg 0 och kod som importeras från alla andra moduler är steg 1. Av detta skäl får endast uttryck från andra moduler skarvas.

Observera att ett steg 1-program är ett steg 0-uttryck av typ Q Exp , Q Type , etc; men konversationen är inte sant - inte alla värden (steg 0-program) av typ Q Exp är ett steg 1-program,

Dessutom, eftersom splitsningar kan kapslas, kan identifierare ha steg som är större än 1. Scenbegränsningen kan sedan generaliseras - ett steg n- program kanske inte körs i något stadium m> n . Till exempel kan man se referenser till sådana steg större än 1 i vissa felmeddelanden:

>: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 |]

Om du använder mall Haskell orsakar inte-in-scope-fel från icke-relaterade identifierare?

Normalt kan alla deklarationer i en enda Haskell-modul ses som alla är ömsesidigt rekursiva. Med andra ord, varje toppdeklaration ligger inom varandras räckvidd i en enda modul. När mall Haskell är aktiverat ändras omfattningsreglerna - modulen bryts istället i grupper med kod separerade av TH-skarvar, och varje grupp är ömsesidigt rekursiv, och varje grupp omfattas av alla ytterligare grupper.

Q-typen

Konstruktören Q :: * -> * definierad i Language.Haskell.TH.Syntax är en abstrakt typ som representerar beräkningar som har åtkomst till kompileringstidsmiljön för modulen där beräkningen körs. Q typen hanterar också variabel substituion, kallad namnupptagning av TH (och diskuteras här .) Alla skarvar har typ QX för vissa X

Miljön för kompileringstid inkluderar:

  • omfattande identifierare och information om nämnda identifierare,
    • typer av funktioner
    • typer och källdatatyper av konstruktörer
    • full specifikation av typdeklarationer (klasser, typfamiljer)
  • platsen i källkoden (rad, kolumn, modul, paket) där skarven inträffar
  • funktionsfixiteter (GHC 7.10)
  • aktiverade GHC-tillägg (GHC 8.0)

Q typen har också förmågan att generera nya namn, med funktionen newName :: String -> Q Name . Observera att namnet inte är bundet någonstans implicit, så användaren måste binda det själva, och så att se till att den resulterande användningen av namnet är välskalad är användarens ansvar.

Q har instanser för Functor,Monad,Applicative och detta är det huvudsakliga gränssnittet för att manipulera Q värden, tillsammans med kombinationerna som tillhandahålls i Language.Haskell.TH.Lib , som definierar en hjälpfunktion för varje konstruktör av TH ast i formen:

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

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

Observera att ExpQ , TypeQ , DecsQ och PatQ är synonymer för AST-typerna som vanligtvis lagras i Q typen.

TH-biblioteket tillhandahåller en funktion runQ :: Quasi m => Q a -> ma , och det finns ett exempel Quasi IO , så det verkar som om Q typen bara är en fin IO . Användningen av runQ :: Q a -> IO a producerar emellertid en IO åtgärd som inte har tillgång till någon kompileringstidsmiljö - detta är endast tillgängligt i den faktiska Q typen. Sådana IO åtgärder kommer att misslyckas vid körning om man försöker komma åt nämnda miljö.

En n-arity curry

Det bekanta

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

funktion kan generaliseras till tupler av godtycklig arity, till exempel:

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

Men att skriva sådana funktioner för tuples av arity 2 till (t.ex.) 20 för hand skulle vara tråkigt (och bortse från det faktum att närvaron av 20 tuples i ditt program nästan säkert signaliserar designproblem som bör fixas med poster).

Vi kan använda Template Haskell för att producera sådana curryN funktioner för godtyckliga n :

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

curryN :: Natural -> Q Exp

curryN funktionen tar ett naturligt tal och producerar curryfunktionen för denna arity, som en Haskell AST.

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

Först producerar vi färskt variabler för vart och ett av argumenten för funktionen - en för ingångsfunktionen och en för var och en av argumenten till nämnda funktion.

  let args = map VarP (f:xs)

Uttrycket args representerar mönstret f x1 x2 .. xn . Observera att ett mönster är en separat syntaktisk enhet - vi kan ta samma mönster och placera det i en lambda, eller en funktionsbindning, eller till och med LHS för en låtbindning (vilket skulle vara ett fel).

      ntup = TupE (map VarE xs)

Funktionen måste bygga argumenttupeln från sekvensen av argument, vilket är vad vi har gjort här. Notera skillnaden mellan mönstervariabler ( VarP ) och uttrycksvariabler ( VarE ).

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

Slutligen är värdet som vi producerar AST \f x1 x2 .. xn -> f (x1, x2, .. , xn) .

Vi kunde också ha skrivit den här funktionen med offert och "lyfta" konstruktörer:

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

Observera att offerten måste vara syntaktiskt giltiga, så [| \ $(map varP (f:xs)) -> .. |] är ogiltigt, eftersom det inte finns något sätt i vanliga Haskell att förklara en 'lista' med mönster - ovanstående tolkas som \ var -> .. och splitsat uttryck förväntas ha typ PatQ , dvs ett enda mönster, inte en lista med mönster.

Slutligen kan vi ladda denna TH-funktion i GHCi:

>: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

Detta exempel anpassas främst härifrån .

Syntax av mall Haskell och kvasikvoter

Mall Haskell aktiveras av förlängningen -XTemplateHaskell GHC. Denna utvidgning möjliggör alla syntaktiska funktioner som är mer detaljerade i detta avsnitt. Den fullständiga informationen om mall Haskell ges av användarhandboken .

skarvar

  • En splice är en ny syntaktisk enhet som aktiveras av Mall Haskell, skriven som $(...) , där (...) är ett uttryck.

  • Det får inte finnas ett avstånd mellan $ och uttryckets första karaktär; och Mall Haskell åsidosätter analysen av $ -operatören - t ex f$g tolkas normalt som ($) fg medan med Mall Haskell aktiverat, analyseras det som en skiva.

  • När en skarv visas på den översta nivån kan $ utelämnas. I detta fall är det skarvade uttrycket hela linjen.

  • En splits representerar kod som körs vid kompileringstiden för att producera en Haskell AST, och som AST kompileras som Haskell-kod och infogas i programmet

  • Skarvar kan visas i stället för: uttryck, mönster, typer och toppdeklarationer. Typen av det skarvade uttrycket, i båda fallen, är Q Exp , Q Pat , Q Type , Q [Decl] . Observera att deklarations skarvar endast kan visas på den översta nivån, medan andra kan vara inuti andra uttryck, mönster eller typer, respektive.

Uttryckskvoteringar (Obs: inte en QuasiQuotation)

  • Ett uttryckskvot är en ny syntaktisk enhet skriven som en av:

    • [e|..|] eller [|..|] - .. är ett uttryck och citatet har typ Q Exp ;
    • [p|..|] - .. är ett mönster och citatet har typ Q Pat ;
    • [t|..|] - .. är en typ och offerten har typ Q Type ;
    • [d|..|] - .. är en lista med deklarationer och offerten har typ Q [Dec] .
  • Ett uttryckskvotering tar ett sammanställningstidsprogram och producerar den AST som representeras av det programmet.

  • Användningen av ett värde i en offert (t.ex. \x -> [| x |] ) utan en skarv motsvarar syntaktiskt socker för \x -> [| $(lift x) |] , där lift :: Lift t => t -> Q Exp kommer från klassen

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

Typade skarvar och offert

  • Typade skarvar är i likhet med tidigare nämnda (otypade) skarvar och skrivs som $$(..) där (..) är ett uttryck.

  • Om e har typ Q (TExp a) så har $$e typ a .

  • Typerade citat har formen [||..||] där .. är ett uttryck av typ a ; den resulterande offerten har typ Q (TExp a) .

  • Typat uttryck kan konverteras till otypade: unType :: TExp a -> Exp .

QuasiQuotes

  • QuasiQuotes generaliserar uttryckskvoteringar - tidigare har den parser som används av uttryckscitationsteckningen en av en fast uppsättning ( e,p,t,d ), men QuasiQuotes tillåter en anpassad parser att definieras och användas för att producera kod vid kompileringstid. Kvasitoteringar kan visas i alla samma sammanhang som vanliga citat.

  • Ett kvasi-offert skrivs som [iden|...|] , där iden är en identifierare av typen Language.Haskell.TH.Quote.QuasiQuoter .

  • En QuasiQuoter består helt enkelt av fyra tolkare, en för var och en av de olika sammanhang där citat kan visas:

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

namn

  • Haskell-identifierare representeras av typen Language.Haskell.TH.Syntax.Name . Namnen bildar bladen på abstrakta syntaxträd som representerar Haskell-program i Mall Haskell.

  • En identifierare som för närvarande är inom räckvidd kan förvandlas till ett namn med antingen: 'e eller 'T I det första fallet, e tolkas i expressions omfattning, medan i det andra fallet T är i typen omfattning (som erinrar om att typer och värde konstruktörer kan dela namnet utan amiguity i Haskell).



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow