Ricerca…


Osservazioni

Che cos'è Template Haskell?

Template Haskell si riferisce alle strutture di meta-programmazione template incorporate in GHC Haskell. Il documento che descrive l'implementazione originale può essere trovato qui .

Quali sono le fasi? (Oppure, qual è la restrizione di scena?)

Le fasi si riferiscono a quando il codice viene eseguito. Normalmente, il codice è indicato solo in fase di esecuzione, ma con Template Haskell, il codice può essere eseguito in fase di compilazione. Il codice "normale" è lo stadio 0 e il codice di compilazione è lo stadio 1.

La limitazione dello stage si riferisce al fatto che un programma stage 0 non può essere eseguito nella fase 1 - questo sarebbe equivalente a essere in grado di eseguire qualsiasi programma regolare (non solo meta-programma) in fase di compilazione.

Per convenzione (e per semplicità di implementazione), il codice nel modulo corrente è sempre lo stadio 0 e il codice importato da tutti gli altri moduli è lo stadio 1. Per questo motivo, solo le espressioni di altri moduli possono essere giuntate.

Si noti che un programma stage 1 è un'espressione stage 0 di tipo Q Exp , Q Type , ecc; ma il contrario non è vero - non ogni valore (programma stage 0) di tipo Q Exp è un programma stage 1,

Inoltre, poiché le giunzioni possono essere annidate, gli identificatori possono avere fasi superiori a 1. La restrizione di fase può quindi essere generalizzata - un programma di stage n non può essere eseguito in nessuno stadio m> n . Ad esempio, in certi messaggi di errore si possono vedere riferimenti a tali fasi superiori a 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 |]

L'uso di Template Haskell causa errori non in ambito da identificatori non correlati?

Normalmente, tutte le dichiarazioni in un singolo modulo Haskell possono essere pensate come tutte reciprocamente ricorsive. In altre parole, ogni dichiarazione di livello superiore è nell'ambito di ogni altro in un singolo modulo. Quando Template Haskell è abilitato, le regole di scoping cambiano - il modulo viene invece suddiviso in gruppi di codice separati dalle giunzioni TH, e ciascun gruppo è reciprocamente ricorsivo e ciascun gruppo nell'ambito di tutti gli altri gruppi.

Il tipo Q

Il costruttore di tipi Q :: * -> * definito in Language.Haskell.TH.Syntax è un tipo astratto che rappresenta calcoli che hanno accesso all'ambiente di compilazione del modulo in cui viene eseguito il calcolo. Il tipo Q gestisce anche la sostituzione parziale, chiamata acquisizione del nome da TH (e discussa qui ). Tutte le giunzioni hanno tipo QX per alcuni X

L'ambiente di compilazione include:

  • identificatori e informazioni in-scope su detti identificatori,
    • tipi di funzioni
    • tipi e tipi di dati di origine dei costruttori
    • specifica completa delle dichiarazioni di tipo (classi, famiglie di tipi)
  • la posizione nel codice sorgente (riga, colonna, modulo, pacchetto) in cui si verifica la giunzione
  • fissità delle funzioni (GHC 7.10)
  • estensioni GHC abilitate (GHC 8.0)

Il tipo Q ha anche la capacità di generare nuovi nomi, con la funzione newName :: String -> Q Name . Si noti che il nome non è associato in alcun modo in modo implicito, quindi l'utente deve collegarlo da sé e pertanto assicurarsi che l'utilizzo risultante del nome sia ben definito è responsabilità dell'utente.

Q ha istanze per Functor,Monad,Applicative e questa è l'interfaccia principale per la manipolazione dei valori Q , insieme ai combinatori forniti in Language.Haskell.TH.Lib , che definiscono una funzione di supporto per ogni costruttore del TH asto della forma:

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

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

Nota che ExpQ , TypeQ , DecsQ e PatQ sono sinonimi per i tipi di AST che sono tipicamente memorizzati all'interno del tipo Q

La libreria TH fornisce una funzione runQ :: Quasi m => Q a -> ma , e c'è un'istanza Quasi IO , quindi sembrerebbe che il tipo Q sia solo un IO fantasioso. Tuttavia, l'uso di runQ :: Q a -> IO a produce un'azione IO che non ha accesso ad alcun ambiente di compilazione - questo è disponibile solo nel tipo Q effettivo. Tali azioni IO falliranno in fase di esecuzione se si tenta di accedere a detto ambiente.

Un curry n-ario

Il familiare

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

la funzione può essere generalizzata a tuple di arbitrarietà, ad esempio:

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

Tuttavia, scrivere tali funzioni per le tuple di arity 2 per (es.) 20 a mano sarebbe noioso (ignorando il fatto che la presenza di 20 tuple nel programma quasi certamente segnala problemi di progettazione che dovrebbero essere corretti con i record).

Possiamo usare Template Haskell per produrre tali funzioni curryN per n arbitrario:

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

curryN :: Natural -> Q Exp

La funzione curryN prende un numero naturale e produce la funzione curry di quell'arity, come Haskell AST.

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

Innanzitutto produciamo nuove variabili di tipo per ciascuno degli argomenti della funzione: uno per la funzione di input e uno per ciascuno degli argomenti di detta funzione.

  let args = map VarP (f:xs)

L'espressione args rappresenta il modello f x1 x2 .. xn . Nota che un pattern è un'entità sintattica separata: potremmo prendere lo stesso pattern e posizionarlo in un lambda, o in un binding di funzione, o anche nel LHS di un let binding (che sarebbe un errore).

      ntup = TupE (map VarE xs)

La funzione deve costruire l'argomento tuple dalla sequenza di argomenti, che è ciò che abbiamo fatto qui. Notare la distinzione tra variabili di pattern ( VarP ) e variabili di espressione ( VarE ).

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

Infine, il valore che produciamo è AST \f x1 x2 .. xn -> f (x1, x2, .. , xn) .

Avremmo potuto anche scrivere questa funzione usando citazioni e costruttori "sollevati":

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

Si noti che le quotazioni devono essere sintatticamente valide, quindi [| \ $(map varP (f:xs)) -> .. |] non è valido, perché in Haskell non esiste alcun modo per dichiarare un 'elenco' di pattern - il precedente è interpretato come \ var -> .. e il si prevede che l'espressione spliced ​​abbia il tipo PatQ , cioè un singolo pattern, non un elenco di pattern.

Infine, possiamo caricare questa funzione TH in 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

Questo esempio è adattato principalmente da qui .

Sintassi di Template Haskell e Quasiquotes

Template Haskell è abilitato -XTemplateHaskell GHC. Questa estensione abilita tutte le caratteristiche sintattiche ulteriormente descritte in questa sezione. I dettagli completi su Template Haskell sono forniti dalla guida dell'utente .

giunzioni

  • Una giuntura è una nuova entità sintattica attivata da Template Haskell, scritta come $(...) , dove (...) è una certa espressione.

  • Non deve esserci uno spazio tra $ e il primo carattere dell'espressione; e Template Haskell sovrascrive l'analisi dell'operatore $ - ad esempio f$g viene normalmente analizzato come ($) fg mentre con Template Haskell abilitato, viene analizzato come una giunzione.

  • Quando una giunzione appare al livello più alto, $ può essere omesso. In questo caso, l'espressione splicing è l'intera linea.

  • Una splice rappresenta un codice che viene eseguito in fase di compilazione per produrre un AST Haskell e che AST è compilato come codice Haskell e inserito nel programma

  • Le giunzioni possono apparire al posto di: espressioni, modelli, tipi e dichiarazioni di livello superiore. Il tipo dell'espressione giuntata, rispettivamente, in entrambi i casi è Q Exp , Q Pat , Q Type , Q [Decl] . Nota che le giunzioni di dichiarazione possono apparire solo al livello superiore, mentre le altre possono essere all'interno di altre espressioni, modelli o tipi, rispettivamente.

Citazioni sulle espressioni (nota: non una QuasiQuotation)

  • Una citazione di espressione è una nuova entità sintattica scritta come una delle seguenti:

    • [e|..|] o [|..|] - .. è un'espressione e la quotazione ha tipo Q Exp ;
    • [p|..|] - .. è un modello e la quotazione ha tipo Q Pat ;
    • [t|..|] - .. è un tipo e la quotazione ha tipo Q Type ;
    • [d|..|] - .. è una lista di dichiarazioni e la quotazione ha tipo Q [Dec] .
  • Una citazione di espressioni richiede un programma a tempo compilato e produce l'AST rappresentato da quel programma.

  • L'uso di un valore in una citazione (ad es. \x -> [| x |] ) senza una splice corrisponde allo zucchero sintattico per \x -> [| $(lift x) |] , dove lift :: Lift t => t -> Q Exp viene dalla classe

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

Giunzioni e citazioni tipizzate

  • Le giunzioni tipizzate sono simili alle giunzioni (non tipizzate) precedentemente menzionate e sono scritte come $$(..) dove (..) è un'espressione.

  • Se e ha tipo Q (TExp a) allora $$e ha tipo a .

  • Le virgolette tipizzate assumono la forma [||..||] dove .. è un'espressione di tipo a ; la citazione risultante ha tipo Q (TExp a) .

  • L'espressione tipizzata può essere convertita in una non tipizzata: unType :: TExp a -> Exp .

QuasiQuotes

  • QuasiQuotes generalizza le citazioni di espressioni: in precedenza, il parser utilizzato dall'espressione quotation è uno di un set fisso ( e,p,t,d ), ma QuasiQuotes consente di definire e utilizzare un parser personalizzato per produrre codice in fase di compilazione. Le quasi-quotazioni possono apparire in tutti gli stessi contesti delle quotazioni regolari.

  • Una quasi-citazione è scritta come [iden|...|] , dove iden è un identificatore di tipo Language.Haskell.TH.Quote.QuasiQuoter .

  • Un QuasiQuoter è composto semplicemente da quattro parser, uno per ciascuno dei diversi contesti in cui possono comparire le citazioni:

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

nomi

  • Gli identificatori di Haskell sono rappresentati dal tipo Language.Haskell.TH.Syntax.Name . I nomi formano le foglie di alberi di sintassi astratti che rappresentano i programmi Haskell in Template Haskell.

  • Un identificatore attualmente in ambito può essere trasformato in un nome con: 'e o 'T Nel primo caso, e viene interpretato nello scope di espressione, mentre nel secondo caso T è nel tipo scope (ricordando che i costruttori di tipi e valori possono condividere il nome senza ambiguità in Haskell).



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow