📜 ⬆️ ⬇️

Introduction to Template Haskell. Part 3. Other aspects of TH

This text is a translation of the Template Haskell documentation written by Bulat Ziganshin. Translation of the entire text is divided into several logical parts to facilitate perception. Further italics in the text - notes of the translator. Previous parts:


Materialization


Materialization (reification) is a Template Haskell tool that allows a programmer to get information from the compiler's symbol table. The monadic function reify ∷ Name → Q Info returns information about the given name: if it is a global identifier (function, constant, constructor), you will get its type, if it is a type or class, you will get its structure. The Info type definition can be found in the Language.Haskell.TH.Syntax module.
Materialization can be used to get a type structure, but you cannot get a function body this way. If you need to materialize the function body, then the function definition needs to be quoted and then you can work with this definition using another template. For example:
 $(optimize [d| fib = … |]) 

or so
 fib = $(optimize [| … |]) 

In fact, the original article says nothing more about materialization. I do not know how much this is a substantive topic - the necessary minimum of knowledge about it is limited to the reify function and the type of Info , but there are some subtleties associated, for example, with the fact that you can get information not about any name. If this topic is interesting, I can collect some information and write a separate note about it (or paste it here).

Lightweight Name Quoting


To get the name ( ∷ Name ) corresponding to the identifier of interest, you can use the mkName function, but this is not a safe solution, because mkName returns an mkName name that can be interpreted differently depending on the context. But the code VarE id ← [| foo |] VarE id ← [| foo |] is safe in this sense, since quoting qualifies names (you get something like My.Own.Module.foo ), but this code is too verbose and requires a monadic context to use. Fortunately, Template Haskell, has another simple form of citing names: 'foo (single quote before foo ) has type Name and contains a qualified name corresponding to the identifier foo , so the code let id = 'foo equivalent to the code VarE id ← [| foo |] VarE id ← [| foo |] . Note that this construct has a simple Name type (not Q Exp or Q Name ), so it can be used where it is not possible to use monads, for example:
 f ∷ ExpExp f (App (Var m) e) | m == 'map = … 

This new form, however, is a quotation and follows the same rules as the quoted brackets [| … |] [| … |] . For example, it cannot be used inside these brackets (this is not possible: [| 'foo |] ), but it cannot be applied to it (this is also impossible: $( 'foo ) ), because you need type Q … to paste it Q … More importantly, this form is defined statically, returning a fully qualified name, with unambiguous interpretation.
Haskell namespaces complicate things a bit. Quote [| P |] [| P |] means the data constructor P , while [t| P |] [t| P |] means a type constructor. Therefore, for the "lightweight citation" requires the same way of separating these entities. For a type context, just two single quotes are used:
A lightweight form of citation is used in the example of generating incarnations of the class Show , which is disassembled at the end.

')

Error messages and recovery


The citation monad allows you to send error messages and recover.
 report ∷ Bool → String → Q () 

The report function displays the message given in the second argument. If the first argument is True , then the result is taken as an error, otherwise, the message is simply shown ( as warning ). In any case, calculations continue, if they need to be stopped, use monadic fail . If there is no “closing” recover , the compilation fails.
 recover ∷ Q a → Q a → Q a 

The call to recover ab runs b . If b were calls to report True "…" , then a is run. If b completes without such errors, then its result is returned, and a ignored.
 location ∷ Q Loc 

Returns the coordinates of the location in the code (in the form of the Loc structure) where the current gluing occurs - it may be convenient for error messages.

Debugging


In order to simplify the debugging of TH programs, GHC supports the special flag -ddump-splices , with which it shows the results of pasting all the top-level patterns during the loading of a module.
In addition, the interpreter can run calculations in the Q monad using the runQ ∷ Q a → IO a function and output the result either as an abstract syntax (AST) or as the corresponding Haskell code:
 $ ghci –XTemplateHaskell … Prelude> :m + Language.Haskell.TH Prelude Language.Haskell.TH> runQ [| \x _ -> x |] >>= print LamE [VarP x_1,WildP] (VarE x_1) Prelude Language.Haskell.TH> runQ [| \x _ -> x |] >>= putStrLn . pprint \x_0 _ -> x_0 

The pprint function prints Haskell code as it will be pasted into the program when compiled. For further examples, we write a simple pattern that generates a lambda expression, ignoring its arguments and returning this string:
 module Cnst where import Language.Haskell.TH cnst :: Int -> String -> Q Exp cnst ns = return (LamE (replicate n WildP) (LitE (StringL s))) 

Now it can be tested in the interpreter:
 $ ghci -XTemplateHaskell Cnst.hs … *Cnst> runQ (cnst 2 "str") >>= print LamE [WildP,WildP] (LitE (StringL "str")) *Cnst> runQ (cnst 2 "str") >>= putStrLn . pprint \_ _ -> "str" 

The same can be done in modules that import templates:
 {-# LANGUAGE TemplateHaskell #-} module Main where import Language.Haskell.TH import Cnst --  cnst    : cnst1 :: t -> [Char] cnst1 = $(cnst 1 "x") cnst2 :: t1 -> t2 -> [Char] cnst2 = $(cnst 2 "str") -- ,    20  cnst20 = $(cnst 20 "foo") --     ,   runQ: main = do runQ(cnst 1 "x") >>= print runQ(cnst 2 "str") >>= print runQ(cnst 20 "foo") >>= print runQ(cnst 1 "x") >>= putStrLn.pprint runQ(cnst 2 "str") >>= putStrLn.pprint runQ(cnst 20 "foo") >>= putStrLn.pprint 


Example: deriveShow



This is a small example that demonstrates how Template Haskell can be used to automatically generate instances of classes. It uses AST constructors, lightweight quotes, quoting brackets, materialization — in general, almost everything that was discussed in this article. For simplicity, the template works only for simple algebraic types (without parameterization, without named fields, etc.). I made a couple of changes (in order to improve readability) in the original code, including showClause function separately.

  {-# LANGUAGE TemplateHaskell #-} module Main where import Derive data T = A Int String | B Integer | C $(deriveShow ''T) main = print [A 1 "s", B 2, C] --    [A 1 "s",B 2,C] 


  {-# LANGUAGE TemplateHaskell #-} module Derive where import Language.Haskell.TH import Control.Monad data T1 = T1 --  ,   -- n       genPE :: Int -> Q ([PatQ], [ExpQ]) genPE n = do ids <- replicateM n (newName "x") return (map varP ids, map varE ids) --     show   : -- show (A x1 x2) = "A "++show x1++" "++show x2 showClause :: Con -> Q Clause showClause (NormalC name fields) = do --  , .. "A". nameBase     let constructorName = nameBase name --          (pats,vars) <- genPE (length fields) --    (" "++show x1++...++"")    [x1, ...] let f [] = [| constructorName |] f (v:vars) = [| " " ++ show $v ++ $(f vars) |] --     clause [conP name pats] -- (A x1 x2) (normalB (f vars)) [] -- "A"++" "++show x1++" "++show x2 --  ,      Show deriveShow :: Name -> Q [Dec] deriveShow t = do --       t TyConI (DataD _ _ _ constructors _) <- reify t --        show: -- show (A x1 x2) = "A "++show x1++" "++show x2 -- show (B x1) = "B "++show x1 -- show C = "C" showbody <- mapM showClause constructors --          --   (T1)    (x = "text")   showbody d <- [d| instance Show T1 where show x = "text" |] let [InstanceD [] (AppT showt (ConT _T1)) [FunD showf _text]] = d return [InstanceD [] (AppT showt (ConT t )) [FunD showf showbody]] 


Conclusion


This concludes the article, it covered the main topics in a volume sufficient to start using Haskell metaprogramming, so we can assume that the introduction to Template Haskell has taken place. To further deepen the topic is invited to look at the official TH page , where you can find links to other articles and many examples.

Source: https://habr.com/ru/post/133009/


All Articles