πŸ“œ ⬆️ ⬇️

Introduction to Template Haskell. Part 1. Required minimum

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.



Template Haskell (further TH) is an extension of Haskell language intended for meta-programming. It enables algorithmic construction of the program at the compilation stage. This allows the developer to use various programming techniques that are not available in Haskell itself, such as macro-like extensions, user-directed optimization (for example, inlining), generalized programming (polytypic programming), generation of auxiliary data structures and functions from existing ones. For example, the code
yell file line = fail ($(printf "Error in file %s line %d") file line) 

can be converted using th in
 yell file line = fail ((\x1 x2 -> "Error in file "++x1++" line "++show x2) file line) 

Another example is code
 data T = A Int String | B Integer | C $(deriveShow ''T) 

can be converted to
 data T = A Int String | B Integer | C instance Show T show (A x1 x2) = "A "++show x1++" "++show x2 show (B x1) = "B "++show x1 show C = "C" 

In TH, Haskell code is generated by ordinary Haskell functions (which I will call templates for clarity) . The minimum you need to know to use TH are the following topics:
  1. How Haskell code is represented in templates (TH functions)
  2. How the citation monad is used to unify names
  3. How the generated TH code is inserted into the program


How Haskell code is presented in templates


In Template Haskell, Haskell code fragments are represented using ordinary algebraic data types . These types are built according to Haskell syntax and represent the abstract syntax tree (AST - abstract syntax tree) of a particular code. There is an Exp type for expressions, Pat for samples, Lit for literals, Dec for declarations, Type for types, etc. Definitions of all these types can be found in the documentation for the Language.Haskell.TH.Syntax module. They are interconnected according to Haskell syntax rules, so using them you can construct values ​​representing any Haskell code fragments. Here are some simple examples:
To make our lives easier, the names of all Exp type constructors end with E , the names of Pat -type constructors end with P , etc. The mkName function used above creates a value of type Name (representing identifiers) from a normal string ( String ), with its contents as a name.
So, to create a Haskell code, the TH function must simply construct and return a value of type Exp (you can also use Dec , Pat or Type ) , which is a representation for a given code fragment. In fact, you do not need to thoroughly study the device of these types in order to know how to submit the Haskell code you need into them. In the section on debugging, I will tell you how to get a TH-representation of a specific Haskell code fragment.
')

How the citation monad is used to unify names


However, patterns are not pure functions that return a simple value of type Exp . Instead, they are calculations in a special Q monad (called the qadtation monad citation monad), which allows you to automatically generate unique names for variables using the monadic function newName :: String -> Q Name . Each time it is called, a new unique name is generated with the given prefix. This name can be used as part of a pattern (using the constructor VarP :: Name -> Pat ) or an expression ( VarE :: Name -> Exp ).
Let's write a simple example - the tupleReplicate template, which, when used as follows: β€œ$(tupleReplicate n) x" , returns an n-tuple with x at all positions (similar to the replicate function for lists). Note that n Is the template argument, and x is the argument of the generated anonymous function (lambda expression). I provide the code for the module containing the definition of this template (the Language.Haskell.TH module provides all the tools needed for working with TH):

 module TupleReplicate where import Language.Haskell.TH tupleReplicate :: Int -> Q Exp tupleReplicate n = do id <- newName "x" return $ LamE [VarP id] (TupE $ replicate n $ VarE id) 

For example, calling β€œ tupleReplicate 3 ” will return an Exp value equivalent to the Haskell expression β€œ (\x -> (x,x,x)) ”.

How the generated TH code is inserted into the program


A splice is written in the form of β€œ $x ”, where x is an identifier, or in the form of $(...) , where the ellipsis implies the corresponding expression. It is important that there is no space between the $ character and the identifier or brackets. This use of $ overrides the value of this symbol as an infix operator, as well as the qualified name Mx overrides the value of the function composition operator β€œ . ". If you need an operator, then the character must be surrounded by spaces.
Insertion may appear in

You should also know that

An example of a module that uses our tupleReplicate template:

 {-# LANGUAGE TemplateHaskell #-} module Test where import TupleReplicate main = do print ($(tupleReplicate 2) 1) --  (1,1) print ($(tupleReplicate 5) "x") --  ("x","x","x","x","x") 

To be continued


The following sections will highlight more interesting and advanced topics:
  1. Citation monad
  2. Quote brackets
  3. Materialization (reification)
  4. Error messages and recovery
  5. Debugging
and the examples of printf and deriveShow mentioned at the beginning are deriveShow .

PS This is my first translation, so I hope for constructive criticism and informative discussion on the topic of the article.

UPDATE:
Part 2. Citation Tools
Part 3. Other aspects of TH

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


All Articles