module Exercise01 where
import ClassyPrelude
import Language.Haskell.TH
Let's start with a simple Template Haskell example. Goals of this exercise:
- Write a simple Hello World splice using the
Q
monad - Debug our splices in GHCi
Consider the following code:
helloWorldE :: Q Exp
helloWorldE = [| putStrLn "hello world" |]
helloWorldD :: Q [Dec]
helloWorldD =
[d| helloWorld :: IO ()
helloWorld = putStrLn "i'm trapped in a splice!"
|]
helloWorldD' :: Q [Dec]
helloWorldD' = do
helloWorldName <- newName "helloWorld"
Just ioName <- lookupTypeName "IO"
Just putStrLnName <- lookupValueName "putStrLn"
sequence $
[ sigD helloWorldName (appT (conT ioName) (tupleT 0))
, valD (varP helloWorldName) (normalB (appE (varE putStrLnName) (litE (stringL "i'm too far gone!!")))) []
]
The above functions do more or less the same thing, but with some interesting differences. helloWorldE
is an Exp
(expression), or code that can be evaluated direcly. helloWorldD
and helloWorldD'
are Dec
s (declarations) which
define new top-level declarations within a module. helloWorldD
and helloWorldD'
are different in that the former is
QuasiQuoted (using the d
identifier) while the latter is written directly in Template Haskell.
Let's load this into GHCi:
cat > .ghci <<EOF
:set prompt "λ → "
:set prompt-cont "λ ‖ "
:set -pgmL markdown-unlit
EOF
stack ghci
:l exercises/Exercise01.lhs
In general, TH splices are called with $(<splice>)
.
We can't invoke all TH splices in GHCi. We can run $(helloWorldE)
but we can't run $(helloWorldD)
. In order to use
the declarations provided by $(helloWorldD)
we'd have to put it in a file and reload the module. In addition, the
splice has to be declared in a separate file from the one in which it is invoked. Try it out for yourself, if you'd
like.
Whenever we want, we can debug our TH splices using a utility. Try it out by replacing <splice>
with a function of
your choice.
putStrLn $(stringE . pprint =<< <splice>)
- There are multiple QuasiQuote identifiers -
[| ... |]
or[e| ... |]
for expressions,[d| ... |]
for declarations,[t| ... |]
for types, and[p| ... |]
for patterns. Name
s are identifiers for both values and types. We can promote a type to aName
to names with two single quotes:''MyType
, and a value to a name with one single quote:'MyDataConstructor
or even'myVariable
.- You can mix QuasiQuotes and splices together:
[| $(StringL "foo") :: String |]
and$(sigE (StringL "foo") [t| String |])
are both valid. - Lowercase TH functions and uppercase TH constructors are related.
varP :: Name -> PatQ
type PatQ = Q Pat
data Pat = ... | VarP Name | ...
- Constructors and functions always end with the first letter of their type.
varE :: Name -> ExpQ
varT :: Name -> TypeQ
varP :: Name -> PatQ