I've recently managed to muster up the courage to try out template Haskell when I noticed I was writing boiler-plate code. As it turns out, Template Haskell is really cool.
I'm translating my notes to English and rewriting them at the same time. This time I'm not using plain LaTeX but rather the Haskell library HaTeX. It allows me to write code in Haskell that generates the appropriate LaTeX code for the book. (Don't face-palm just yet.)
Boiler-plate Haskell (?!)
Haskell is not known for being very verbose but I noticed I was writing the same code over and over again.
For any term I define, there are five functions. In the case of the definitions for a mathematical relation, these are:
relation'
: emphasized (bold) and indexed versionrelation
: indexed (but not emphasized)relation_
: indexed with reference to the definition in a footnoterelationDefinitionLabel
: Label for the definition of a relation (required for referencing)relationDefinition
: The actual definition for a relation
relation' :: Note
= term "relation"
relation'
relation :: Note
= ix "relation"
relation
relation_ :: Note
= relation <> ref relationDefinitionLabel
relation_
relationDefinitionLabel :: Label
= Label Definition "relation"
relationDefinitionLabel
relationDefinition :: Note
= de $ do
relationDefinition
lab relationDefinitionLabel"A ", relation', " between ", m n, " sets ", m $ cs [x 1, x 2, dotsc, x n], " is a subset of their ", carthesianProduct_]
s [where
= "n"
n = "X" !: i x i
In case you're interested, this Haskell code produces the following LaTeX code. As you can see, the haskell code is currently a lot longer because of the boiler plate functions: relation'
, relation
, relation_
, relationDefinitionLabel
, and relationDefinition
.
\begin{definition}
\label{Definition:relation}
\index{relation}\textbf{relation} between $n$ sets ${X}_{1}, {X}_{2}, \dotsc{}, {X}_{n}$ is a subset of their Carthesian product\footnote{See Definition \ref{Definition:carthesian-product} on page \pageref{Definition:carthesian-product}.}.
A \end{definition}
Fixing it with Template Haskell
What if I told you Haskell could generate Haskell code during compilation and use it immediately after. That's exactly what Template Haskell is for!
Don't confuse the 'template' in 'Template Haskell' with the 'template' in 'C++ templates'. C++ templates are nothing like Template Haskell'. Template haskell is more like advanced in-AST macro's in Haskell. Instead of macro-ing text with a macro-language that then gets parsed into an AST, you macro the AST itself in Haskell!
I won't go into the nitty gritty details of how exactly you implement the generation of this code, but the high level concept looks as follows.
You define a list of Dec
s (Declarations) in the Q
monad and then you can just put that function at the top of a Haskell file to put the generated Haskell code in there with the rest of the code.
For example, getting the hostname of the machine that compiled the code would be impossible without a compile-time reference. You could use template-haskell for this. For example, you could define a function host :: Q [Dec]
that generates code for hostname = "<compiling-machines-hostname>"
at compile-time. Then you would only have to add host
at the top of your module and the rest of the code will have hostname
at its disposal. (Make sure to add {-# LANGUAGE TemplateHaskell #-}
at the top of that module and define host
in a module other than the one where you want to use it.)
host :: Q [Dec] host = do hn <- runIO getHostName let name = mkName "hostname" sig = SigD name $ ConT $ mkName "String" dec = FunD name [Clause [] (NormalB . LitE . StringL $ hn) []] return [sig, dec]
... generates ...
hostname :: String hostname = "myHostName"
This probably looks like magic to someone unfamiliar with Template Haskell (I know it did to me!) If you would like to learn more, I suggest you try it out yourself and have a look at the excellent Template Haskell documentation. Don't be scared of all the constructors. They will make sense once you understand what the abbreviations stand for.
Of course if I don't go into the details of this simple function, I won't go into details of the function makeDefs
that generates the boiler-plate code I mentioned before. You can find the full code in the notes repository.
Suffice it to say that I no longer write this boiler-plate code. I now just write makeDefs ["relation"]
at the top of the module and the boiler-plate relation'
, relation
, relation_
and relationDefinitionLabel
is just generated for me at compile-time.
Reflection
Template Haskell is amazing, but other than the inherent geekyness of generating Haskell with Haskell, are there other advantages? Absolutely, if I made a typo in relation
, before it would have shown up somewhere inconsistently, now it is either consistently correct or wrong everywhere. Automatic consistency is not the only advantage. Code-size (before compile-time) is smaller and less error-prone without sacrificing any type-safety. Granted, Template Haskell can generate non-type-safe code but then that code will not compile.