32
loading...
This website collects cookies to deliver better user experience
e
, p
, t
and d
.template-haskell
package and import the Language.Haskell.TH
module, which is our main interest for this article. Note that we will also need to activate the TemplateHaskell
language extension.>>> import Language.Haskell.TH
>>> :set -XTemplateHaskell
[e| ... |]
quoter, which takes in some Haskell expression and returns an AST representing that particular expression.>>> runQ [e|1 + 2|]
InfixE (Just (LitE (IntegerL 1))) (VarE GHC.Num.+) (Just (LitE (IntegerL 2)))
runQ :: Quasi m => Q a -> m a
, we can extract our quotation, which is then printed by GHCi. We will see later that IO
and Q
are two instances of the Quasi
typeclass, and so we are able to use it from our interactive interpreter.InfixE
/ | \
Just VarE Just
/ | \
LitE GHG.Num.+ LitE
/ \
IntegerL IntegerL
/ \
1 2
Just
has appeared in the operands. An expression such as [e|(1 +)|]
would instead have Nothing
as its second operand!template-haskell
library. For example, instead of writing [e|(4, 2)|]
, we may as well type out the full AST to obtain the same result:>>> runQ (pure (TupE [Just (LitE (IntegerL 4)), Just (LitE (IntegerL 2))]))
TupE [Just (LitE (IntegerL 4)),Just (LitE (IntegerL 2))]
TupE
|
:
/ \
Just \
/ :
LitE / \
/ Just []
IntegerL |
/ LitE
4 |
IntegerL
|
2
e
, p
, d
, and t
.[e| ... |]
is so common that we can just use [| ... |]
instead.e
to generate Haskell expressions so far, although it is also of interest to create other Haskell constructs, such as declarations. Without further ado, let’s use Template Haskell to generate a declaration that is equivalent to the following Haskell code:decl :: Int
decl = 1 + 2
d
quasi-quoter for this:>>> runQ [d|
... decl :: Int
... decl = 1 + 2
... |]
[ SigD decl_0 (ConT GHC.Types.Int)
, ValD (VarP decl_0) (
NormalB ( -- NormalB is the body of a declaration without pattern guards
InfixE (Just (LitE (IntegerL 1))) (VarE GHC.Num.+) (Just (LitE (IntegerL 2)))
)
) []
]
-- A function which takes two other functions and composes them, as (.):
>>> compose :: Q Exp
... compose = [|\left right x -> left (right x)|]
>>> $compose (* 2) (+ 1) 0
2
$compose
? This is a splice, and it allows us to use a given Template Haskell definition. The expression that immediately follows the $
will evaluate the TH definition and return an actual Haskell definition that we can use. Splices will try to inject any kind of code that is given to them, so make sure it is correct. As demonstrated below, we can inject a bound variable in Template Haskell:>>> x = 20
>>> compose' :: Q Exp
... compose' = [|\left right -> left (right x)|]
>>> $compose' (* 2) (+ 1)
42
x
with a variable that was not bound, such as z
, we get a Variable not in scope: z
error instead during the splice generation. Any malformed expressions will be reported like any other regular Haskell expression. In other words, we analyze a Template Haskell declaration as if it was any other ordinary Haskell declaration.Main.hs
with the following:{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
someSplice :: Q [Dec]
someSplice = [d|y = 0|]
x :: Int
x = 42
$someSplice
z :: String
z = show x
>>> :l Main.hs
[1 of 1] Compiling Main ( Main.hs, interpreted )
Failed, no modules loaded.
Main.hs:11:1: error:
GHC stage restriction:
‘someSplice’ is used in a top-level splice, quasi-quote, or annotation,
and must be imported, not defined locally
|
11 | $someSplice
| ^^^^^^^^^^^
someSplice
to a new TH.hs
file.{-# LANGUAGE TemplateHaskell #-}
module TH where
import Language.Haskell.TH
someSplice :: Q [Dec]
someSplice = [d|y = 0|]
Main.hs
to import it:{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
import TH
x :: Int
x = 42
$someSplice
z :: String
z = show x
Main
and use y
.x
and z
are declared:z :: String
z = show x
$someSplice
x :: Int
x = 42
Main.hs:8:10: error:
• Variable not in scope: x
• ‘x’ (line 13) is not in scope before the splice on line 10
|
8 | z = show x
|
x
was in the scope of z
but not the opposite, but after swapping both declarations, z
is in the scope of x
but not the opposite. To mitigate the problem, we often try to insert all splices at the very bottom of the file, if possible.-ddump-splices
to see the output:>>> :set -ddump-splices
Main
again (this time with the correct order of declarations), we should see the generated output of the splice:>>> :r
[2 of 2] Compiling Main ( Main.hs, interpreted )
Main.hs:10:1-11: Splicing declarations
someSplice ======> y_a65Q = 0
Ok, two modules loaded.
y
was declared as 0
.lens
library probably have found methods such as _1
, _2
, _3
, etc. to deal with tuples of arbitrary sizes. Let’s try to generate these instances automatically using TH.Tuple2
, Tuple3
, … for tuples of arbitrary sizes. Each of them will have methods _1
, _2
, _3
, … for accessing elements of tuples. After this, we will generate instances for each (a, b)
, (a, b, c)
, (a, b, c, d)
, etc.TuplesTH.hs
:{-# LANGUAGE TemplateHaskell #-}
module TuplesTH where
import Control.Monad (unless)
import Data.Traversable (for)
import Language.Haskell.TH
>>> runQ [d|x :: (a,b,c); x = undefined|]
[ SigD x_8 (AppT (AppT (AppT (TupleT 3) (VarT a_5)) (VarT b_6)) (VarT c_7))
, ValD (VarP x_8) (NormalB (VarE GHC.Err.undefined)) []
]
AppT (AppT (AppT (TupleT 3) (VarT a_5)) (VarT b_6)) (VarT c_7)
:AppT
/ \
AppT VarT
/ \ \
AppT VarT c_7
/ \ \
TupleT VarT b_6
/ \
3 a_5
(a, b, ..., t, ...) -> t
where t
is the n-th type of our tuple. Again, let’s see what is the Template Haskell equivalent for a function:>>> runQ [d|x :: a -> b; x = undefined|]
[ SigD x_15 (AppT (AppT ArrowT (VarT a_13)) (VarT b_14))
, ValD (VarP x_15) (NormalB (VarE GHC.Err.undefined)) []
]
(->)
is represented as ArrowT
. Keep in mind that a -> b
is the same as (->) a b
.AppT
/ \
AppT VarT
/ \ \
ArrowT VarT b_14
\
a_13
Dec
we find our constructor for declaring a class: ClassD Cxt Name [TyVarBndr ()] [FunDep] [Dec]
. This class should have the form class TupleX t r | t -> r where _X :: t -> r
, where X
is the number of elements in the tuple. With this information, we can declare a function which will generate such class for us:generateTupleClass :: Int -> Q [Dec]
generateTupleClass size = do
unless (size > 0) $
fail $ "Non-positive size: " ++ size'
pure [cDecl]
where
size' = show size
className = mkName ("Tuple" ++ size')
methodName = mkName ('_' : size')
t = mkName "t"
r = mkName "r"
-- class TupleX t r | t -> r where
cDecl = ClassD [] className [PlainTV t, PlainTV r] [FunDep [t] [r]] [mDecl]
-- _X :: t -> r
mDecl = SigD methodName (AppT (AppT ArrowT (VarT t)) (VarT r))
mkName
function to create the names of our class (TupleX
), its method (_X
), and the t
and r
type variables.ClassD
constructor with the following fields:[] :: Ctx
: An empty array of constraints, since we don’t impose further restrictions on our types;className :: Name
: The name of the class being declared;[PlainTV t, PlainTV r] :: [TyVarBndr ()]
: The bindings of our typeclass, which are t
and r
;[FunDep [t] [r]] :: [FunDep]
: A functional dependency of the format t -> r
;[mDecl] :: [Dec]
: Our class declarations, containing _X
, a method of type t -> r
.Main.hs
and test out what we made:{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell #-}
import TuplesTH
$(generateTupleClass 3)
>>> :l Main
[2 of 2] Compiling Main ( Main.hs, interpreted )
Ok, two modules loaded.
>>> :i Tuple3
type Tuple3 :: * -> * -> Constraint
class Tuple3 t r | t -> r where
_3 :: t -> r
{-# MINIMAL _3 #-}
-- Defined at Main.hs:8:3
ClassD
, we can use InstanceD
for this, which in Dec
has a constructor of the form InstanceD (Maybe Overlap) Ctx Type [Dec]
. What should the AST for the instance look like? Let GHCi come to our rescue again!>>> runQ [d|instance Tuple3 (a, b, c) c where _3 (_, _, c) = c|]
[InstanceD
Nothing
[]
(AppT (AppT (ConT Main.Tuple3) (AppT (AppT (AppT (TupleT 3) (VarT a_4)) (VarT b_5)) (VarT c_6))) (VarT c_6))
[FunD
Main._3 [Clause [TupP [WildP,WildP,VarP c_7]] (NormalB (VarE c_7)) []]]]
AppT
for our tuple type like before. What’s interesting right now is the root of this type, which is AppT (AppT (ConT Main.Tuple3)) (VarT c_7)
.InstanceD
also expects a declaration, in our case a function, represented as FunD
. We want a pattern for our tuple where the X-th tuple element has a variable pattern and the other ones are wildcards. We can create a function that takes the index of the element we want to generate a getter for and the total size of the tuple and generates an instance accessing this element._1
, _2
, _3
, etc (up to the tuple size) and generate a sequence of nested AppT
, with TupleT x
at the bottom, as in the tree we’ve seen before. This will let us create our tuple type (_1, _2, ...)
. With this, we can generate a type TupleX (_1, _2, ...) _X
which is the instance declaration for our class._X (_, _, ..., x, ...) = x
that corresponds to our accessor.generateTupleInstance :: Int -> Int -> Q [Dec]
generateTupleInstance element size = do
unless (size > 0) $
fail $ "Non-positive size: " ++ element'
unless (size >= element) $
fail $ "Can't extract element " ++ element' ++ " of " ++ size' ++ "-tuple"
pure [iDecl]
where
element' = show element
size' = show size
className = mkName ("Tuple" ++ element')
methodName = mkName ('_' : element')
x = mkName "x"
vars = [mkName ('_' : show n) | n <- [1..size]]
signature = foldl (\acc var -> AppT acc (VarT var)) (TupleT size) vars
-- instance TupleX (_0, _1, ..., _X, ...) _X where
iDecl = InstanceD Nothing [] (AppT (AppT (ConT className) signature) (VarT $ mkName ('_' : element'))) [mDecl]
-- _X (_, _, ..., x, ...) = x
mDecl = FunD methodName [Clause [TupP $ replicate (element - 1) WildP ++ [VarP x] ++ replicate (size - element) WildP] (NormalB $ VarE x) []]
$(generateTupleInstance 3 5)
to our Main
module and load it in GHCi, we can see that this indeed generated an instance:>>> :i Tuple3
type Tuple3 :: * -> * -> Constraint
class Tuple3 t r | t -> r where
_3 :: t -> r
{-# MINIMAL _3 #-}
-- Defined at Main.hs:8:3
instance Tuple3 (_1, _2, _3, _4, _5) _3 -- Defined at Main.hs:9:3
>>> _3 (42, "hello", '#', [], 3.14)
'#'
generateTupleBoilerplate :: Int -> Q [Dec]
generateTupleBoilerplate size =
concatFor [1..size] $ \classDeclIndex -> do
cDecl <- generateTupleClass classDeclIndex
iDecls <- for [1..classDeclIndex] $ \instanceDeclIndex ->
generateTupleInstance instanceDeclIndex classDeclIndex
pure $ concat (cDecl : iDecls)
where
concatFor xs = fmap concat . for xs
Main
to contain $(generateTupleBoilerplate 62)
and voilá! We have all instances for tuples.TupleX
so that each class declaration also contains a Tuple(X-1)
constraint. For example, class Tuple3 a => Tuple2 a
, class Tuple1 a => Tuple2 a
etc. Notice Tuple1
should have no constraints.mapX
method that maps the X-th element of a tuple, similarly to fmap
.32