module Exercise01 where
import ClassyPrelude
import Language.Haskell.THLet's start with a simple Template Haskell example. Goals of this exercise:
- Write a simple Hello World splice using the
Qmonad - 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 Decs (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.lhsIn 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. Names are identifiers for both values and types. We can promote a type to aNameto names with two single quotes:''MyType, and a value to a name with one single quote:'MyDataConstructoror 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 -> PatQtype PatQ = Q Patdata Pat = ... | VarP Name | ...
- Constructors and functions always end with the first letter of their type.
varE :: Name -> ExpQvarT :: Name -> TypeQvarP :: Name -> PatQ