Recherche…


Remarques

Qu'est-ce que Template Haskell?

Template Haskell fait référence aux fonctions de méta-programmation de modèles intégrées à GHC Haskell. Le document décrivant la mise en œuvre originale peut être trouvé ici .

Quelles sont les étapes? (Ou quelle est la restriction de la scène?)

Les étapes se réfèrent au moment où le code est exécuté. Normalement, le code n'est exécuté qu'au moment de l'exécution, mais avec Template Haskell, le code peut être exécuté au moment de la compilation. Le code "normal" est l'étape 0 et le code de compilation est l'étape 1.

La restriction d'étape fait référence au fait qu'un programme d'étape 0 peut ne pas être exécuté à l'étape 1 - cela équivaudrait à être capable d'exécuter un programme régulier (pas seulement un méta-programme) au moment de la compilation.

Par convention (et dans un souci de simplicité d'implémentation), le code du module en cours est toujours l'étape 0 et le code importé de tous les autres modules est l'étape 1. Pour cette raison, seules les expressions d'autres modules peuvent être épissées.

Notez qu'un programme d'étape 1 est une expression d'étape 0 du type Q Exp , Q Type , etc. mais l'inverse n'est pas vrai - toutes les valeurs (programme de l'étape 0) de type Q Exp sont pas des programmes de l'étape 1,

De plus, puisque les épissures peuvent être imbriquées, les identificateurs peuvent avoir des stades supérieurs à 1. La restriction d'étape peut alors être généralisée - un programme d'étape n peut ne pas être exécuté à n'importe quelle étape m> n . Par exemple, on peut voir des références à ces étapes supérieures à 1 dans certains messages d'erreur:

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

Utilisation de Template Haskell provoque des erreurs non liées à la portée des identificateurs sans lien?

Normalement, toutes les déclarations dans un seul module Haskell peuvent être considérées comme toutes récursives. En d'autres termes, chaque déclaration de niveau supérieur est dans le cadre de chaque autre dans un seul module. Lorsque Template Haskell est activé, les règles de portée changent - le module est plutôt divisé en groupes de code séparés par TH épissures, et chaque groupe est mutuellement récursif, et chaque groupe est dans la portée de tous les autres groupes.

Le type Q

Le constructeur de type Q :: * -> * défini dans Language.Haskell.TH.Syntax est un type abstrait représentant des calculs ayant accès à l'environnement de compilation du module dans lequel le calcul est exécuté. Le type Q gère également la substitution variable, appelée capture de nom par TH (et discuté ici ). Toutes les épissures ont le type QX pour certains X

L'environnement de compilation comprend:

  • des identificateurs dans la portée et des informations sur lesdits identificateurs,
    • types de fonctions
    • types et types de données source des constructeurs
    • spécification complète des déclarations de type (classes, familles de types)
  • l'emplacement dans le code source (ligne, colonne, module, package) où l'épissure se produit
  • fixités des fonctions (GHC 7.10)
  • extensions GHC activées (GHC 8.0)

Le type Q a également la capacité de générer de nouveaux noms, avec la fonction newName :: String -> Q Name . Notez que le nom n'est lié de manière implicite, de sorte que l'utilisateur doit le lier lui-même et que l'utilisateur doit donc s'assurer que l'utilisation du nom qui en résulte est bien définie.

Q a des instances pour Functor,Monad,Applicative et c'est l'interface principale pour manipuler les valeurs Q , ainsi que les combinateurs fournis dans Language.Haskell.TH.Lib , qui définissent une fonction d'assistance pour chaque constructeur de la th TH du formulaire:

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

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

Notez que ExpQ , TypeQ , DecsQ et PatQ sont des synonymes pour les types AST qui sont généralement stockés dans le type Q

La bibliothèque TH fournit une fonction runQ :: Quasi m => Q a -> ma , et il y a une instance Quasi IO , il semblerait donc que le type Q ne soit qu'un IO sophistiqué. Cependant, l'utilisation de runQ :: Q a -> IO a produit une IO action n'a pas accès à tout environnement de compilation - ce n'est disponible dans le réel Q de type. Ces actions IO échoueront lors de l'exécution si vous essayez d'accéder à cet environnement.

Un curry n-arité

Le familier

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

la fonction peut être généralisée à des tuples d'arité arbitraire, par exemple:

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

Cependant, écrire de telles fonctions pour des tuples d'arity 2 à (par exemple) 20 à la main serait fastidieux (et ignorer le fait que la présence de 20 tuples dans votre programme signalent presque certainement des problèmes de conception qui devraient être corrigés avec des enregistrements).

Nous pouvons utiliser Template Haskell pour produire de telles fonctions curryN pour n arbitraire:

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

curryN :: Natural -> Q Exp

La fonction curryN prend un nombre naturel et produit la fonction curry de cette arité, en tant que AST Haskell.

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

Nous produisons d'abord des variables de type fraîches pour chacun des arguments de la fonction - une pour la fonction d'entrée et une pour chacun des arguments de cette fonction.

  let args = map VarP (f:xs)

L'expression args représente le motif f x1 x2 .. xn . Notez qu'un modèle est une entité syntaxique distincte - nous pourrions prendre ce même modèle et le placer dans un lambda, ou une liaison de fonction, ou même le LHS d'une liaison let (ce qui serait une erreur).

      ntup = TupE (map VarE xs)

La fonction doit générer l'argument tuple à partir de la séquence d'arguments, ce que nous avons fait ici. Notez la distinction entre les variables de modèle ( VarP ) et les variables d’expression ( VarE ).

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

Enfin, la valeur que nous produisons est l’AST \f x1 x2 .. xn -> f (x1, x2, .. , xn) .

Nous aurions aussi pu écrire cette fonction en utilisant des citations et des constructeurs 'levés':

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

Notez que les citations doivent être syntaxiquement valides, donc [| \ $(map varP (f:xs)) -> .. |] n'est pas valide, car il n'existe aucun moyen pour Haskell de déclarer une liste de motifs - ce qui précède est interprété comme \ var -> .. et le l'expression PatQ devrait avoir le type PatQ , c'est-à-dire un seul motif, pas une liste de motifs.

Enfin, nous pouvons charger cette fonction TH dans 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

Cet exemple est adapté principalement à partir d' ici .

Syntaxe du modèle Haskell et Quasiquotes

Template Haskell est activé par l'extension -XTemplateHaskell GHC. Cette extension active toutes les fonctionnalités syntaxiques détaillées dans cette section. Les détails complets sur Template Haskell sont donnés dans le guide de l' utilisateur .

Épissures

  • Une épissure est une nouvelle entité syntaxique activée par Template Haskell, écrite sous la forme $(...) , où (...) est une expression.

  • Il ne doit pas y avoir d'espace entre $ et le premier caractère de l'expression; et Template Haskell remplace l'analyse syntaxique de l'opérateur $ - par exemple, f$g est normalement analysé en tant que ($) fg alors qu'avec Template Haskell activé, il est analysé en tant qu'épissure.

  • Lorsqu'une épissure apparaît au niveau supérieur, $ peut être omis. Dans ce cas, l'expression épissée est la ligne entière.

  • Une épissure représente le code qui est exécuté au moment de la compilation pour produire un Haskell AST, et que AST est compilé en tant que code Haskell et inséré dans le programme.

  • Les épissures peuvent apparaître à la place de: expressions, modèles, types et déclarations de niveau supérieur. Le type de l'expression épissée, dans chaque cas respectivement, est Q Exp , Q Pat , Q Type , Q [Decl] . Notez que les épissures de déclaration ne peuvent apparaître qu'au niveau supérieur, tandis que les autres peuvent se trouver dans d'autres expressions, modèles ou types, respectivement.

Citations d'expression (note: pas une QuasiQuotation)

  • Une citation d'expression est une nouvelle entité syntaxique écrite comme l'une des suivantes:

    • [e|..|] ou [|..|] - .. est une expression et la citation a le type Q Exp ;
    • [p|..|] - .. est un motif et la citation a le type Q Pat ;
    • [t|..|] - .. est un type et la citation a le type Q Type ;
    • [d|..|] - .. est une liste de déclarations et la citation a le type Q [Dec] .
  • Une citation d'expression prend un programme de compilation et produit l'AST représentée par ce programme.

  • L'utilisation d'une valeur dans une citation (par exemple, \x -> [| x |] ) sans épissure correspond au sucre syntaxique pour \x -> [| $(lift x) |] , où lift :: Lift t => t -> Q Exp vient de la classe

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

Épissures et citations typées

  • Les épissures typées sont similaires aux épissures (non typées) mentionnées précédemment et sont écrites sous la forme $$(..)(..) est une expression.

  • Si e a le type Q (TExp a) alors $$e a le type a .

  • Les citations typées prennent la forme [||..||].. est une expression de type a ; la citation résultante a le type Q (TExp a) .

  • Les expressions typées peuvent être converties en expressions non typées: unType :: TExp a -> Exp .

QuasiQuotes

  • QuasiQuotes généraliser les citations d'expression - auparavant, l'analyseur utilisé par l'expression entre guillemets fait partie d'un ensemble fixe ( e,p,t,d ), mais QuasiQuotes permet de définir un analyseur personnalisé et de le générer au moment de la compilation. Les quasi-citations peuvent apparaître dans tous les mêmes contextes que les citations régulières.

  • Une quasi-citation est écrite comme [iden|...|] , où iden est un identifiant de type Language.Haskell.TH.Quote.QuasiQuoter .

  • Un QuasiQuoter est simplement composé de quatre analyseurs, un pour chacun des différents contextes dans lesquels les citations peuvent apparaître:

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

Des noms

  • Les identificateurs Haskell sont représentés par le type Language.Haskell.TH.Syntax.Name . Les noms forment les feuilles des arbres de syntaxe abstraites représentant les programmes Haskell dans Template Haskell.

  • Un identifiant qui est actuellement dans la portée peut être transformé en un nom avec: 'e ou 'T Dans le premier cas, e est interprété dans la portée de l'expression, tandis que dans le second cas, T est dans le type scope (en rappelant que les constructeurs de types et de valeurs peuvent partager le nom sans amiguité dans Haskell).



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow