References:
- https://hackage.haskell.org/package/template-haskell
- https://wiki.haskell.org/Template_Haskell
- https://web.archive.org/web/20170331032455/www.hyperedsoftware.com/blog/entries/first-stab-th.html
- Splices: use definitions made with TH
Q
, the quotation monad, seems to be the main thing.
- VarP: variable type??
- LamE: lambda
- LitE: lambda body??
- IntegerL: Integer literal
- Exp: expression type
- Name: name type
- Dec: type of declarations?? (as in
Q Dec
)- Eg: Type class instances are declarations.
General:
- Import with:
import Language.Haskell.TH.Syntax
Lift
typeclassderive Lift
possible withDeriveLift
ghc extension
- 'Lift' => quote, 'splice' => unquote
- Normal splicing:
$name
- Type-level expression splicing:
$$name
unType :: TExp a -> Exp a
: Remove type info- Typed quotes:
[|| ‥ ||]
(E: Expresssion, P: Pattern, L: Literal?, T: type?)
ExpQ is an alias for Q Exp
Example use:
Clash.Prelude Language.Haskell.TH> runQ [| \x -> 1 |]
LamE [VarP x_0] (LitE (IntegerL 1))
Clash.Prelude Language.Haskell.TH> :t it
it :: Language.Haskell.TH.Exp
Prelude> :set -XTemplateHaskell
Prelude> import Language.Haskell.TH.Syntax
Prelude Language.Haskell.TH.Syntax>
Quoting with quotes
Non-type values can be quoted with one single quote:
Prelude Language.Haskell.TH.Syntax> :t 'id
'id :: Name
Prelude Language.Haskell.TH.Syntax> 'id
GHC.Base.id
Prelude Language.Haskell.TH.Syntax> :t id
id :: a -> a
Type values can be quoted with two single quotes:
Prelude Language.Haskell.TH.Syntax> ''Bool
GHC.Types.Bool
Prelude Language.Haskell.TH.Syntax> :t ''Bool
''Bool :: Name
Prelude Language.Haskell.TH.Syntax> 'Bool
<interactive>:31:1: error:
• Not in scope: data constructor ‘Bool’
• In the Template Haskell quotation 'Bool
Builtins
- VarE, VarP, etc: variables
- LamX: lambda expression
- InfixE: Infix expression
- IntegerL: Integer literal
Prelude Language.Haskell.TH.Syntax> :t LamE
LamE :: [Pat] -> Exp -> Exp
Prelude Language.Haskell.TH.Syntax> :t VarP
VarP :: Name -> Pat
The TemplateHaskell extension got to enable for some stuff to work:
Prelude Language.Haskell.TH.Syntax> runQ [e| Just x |]
<interactive>:20:18: error: parse error on input ‘]’
Prelude Language.Haskell.TH.Syntax> :set -XTemplateHaskell
Prelude Language.Haskell.TH.Syntax> runQ [e| Just x |]
AppE (ConE GHC.Maybe.Just) (UnboundVarE x)
Stage restriction
varname is used in a top-level splice, quasi-quote, or annotation,
and must be imported, not defined locally
—
From https://well-typed.com/blog/2020/06/th-for-static-data/:
However if we define isStaticIdSet and the Lift instance in the same module, GHC can’t call liftTyped as it’s not yet compiled by the time we need it.
Conventions
Last letter of variable names usually follow this convention:
- VarE: Q Exp
- Var?: Q TExp ??
- VarD: Q Dec
- VarT: Q Type
- VarP: Q Pat
Info
- The
[| ... |]
brackets are known as 'Oxford brackets'. It reifies haskell (ie, concrete haskell syntax -> AST) - The
$( ... )
does the de-reification. In TH parlance, it's called splicing.- We can't splice a function defined in the same file. Importing from other files is okay, though.
Some Syntax
From https://markkarpov.com/tutorial/th.html:
Declaration | [d | ‥ | ] | Q [Dec] |
Expression | [e | ‥ | ] | Q Exp |
Typed expression | [ | ‥ | ||
Type | [t | ‥ | ] | Q Type |
Pattern | [p | ‥ | ] | Q Pat |
Some types
https://hackage.haskell.org/package/template-haskell-2.23.0.0/docs/Language-Haskell-TH.html
- type Pred = Type
- type Kind = Type
- type Cxt = [Pred]
- TyVarBndr
Misc
> import Language.Haskell.TH
λ> a = mkName "hi"
λ> :t a
λa :: Name
ghci> :t reify ''Bool
reify ''Bool :: Q Info
-- https://github.com/seanwestfall/templatehaskell
ghci> $(stringE . show =<< reify ''Bool)
"TyConI
(DataD
[]
GHC.Types.Bool
[]
Nothing
[NormalC GHC.Types.False [],
NormalC GHC.Types.True []]
[])"
ghci> :t DataD [] (mkName "Foo") [] Nothing
DataD [] (mkName "Foo") [] Nothing :: [Con] -> [DerivClause] -> Dec
ghci> [d| data Foo = Foo Int |]
[DataD [] Foo_1 [] Nothing [NormalC Foo_2 [(Bang NoSourceUnpackedness NoSourceStrictness,ConT GHC.Types.Int)]] []]
ghci> [d| data Itype = C0 | C1 |]
[DataD [] Itype_3 [] Nothing [NormalC C0_4 [],NormalC C1_5 []] []]
ghci> [d| data Itype = C0 | C1 | C2 |]
[DataD [] Itype_6 [] Nothing [NormalC C0_7 [],NormalC C1_8 [],NormalC C2_9 []] []]
ghci> normalC (mkName "C0") []
NormalC C0 []
ghci> dataD (pure []) (mkName "Itype") [] Nothing [] []
DataD [] Itype [] Nothing [] []