Writing a book in Haskell
Yes, you read that right. Not on Haskell, but in Haskell.
I have written quite a bit of LaTeX in the past but most of it was in my native language. Since that language is not English I figured I would write my next bits in English so that it would be helpful for more people.
I intended to write a collection of notes on mathematical subjects, because that is how I study and math ages well anyway. However, LaTeX just was not going to be good enough. It allows for too many mistakes, does not impose discipline and has really ugly syntax.
I had been thinking about writing a DSL that compiles to LaTeX because there were some features missing from LaTeX:
- Infix macro’s.
- Curried macro’s.
- Local constants
- Painless subpart compilation.
- Generation of graphics.
- Caching of dependencies.
- Parallel resolution of those dependencies.
- Nice Turing-complete programming for the generation of certain repetitive parts.
- Compile-time safety with respect to references.
I would write my notes in this new language and the code would then be compiled down to LaTeX before it was compiled to a pdf file. Moreover, when compiling only a part of the code, only for that part would the LaTeX be generated.
I quickly realized that writing such a DSL was going to take an enormous amount of time. It would take so much time that I would never even get to writing my notes.
As it happens, Haskell is really good for writing EDSL’s, and the HaTeX library already existed so I would not have to reinvent the wheel. The combination of Haskell and HaTeX already had most of the features I was looking for. Functions can be used as LaTeX macro’s and they can already be curried. Haskell has local constants and is Turing-complete.
The other features I would have to implement myself. I set out to create what would eventually become the back-end system for The Notes.
The idea is to have the writer focus on writing and not on how to swashbuckle with annoying syntax and repetitive LaTeX code.
The building block of these notes is a data type called
Note is a Monad that takes care of all the required features
while providing a clean interface to write my notes.
also an instance of
IsString such that I can write literal
strings and they will end up in the notes as expected.
When writing LaTeX, we often find that we need some external package, add it to the preamble, and then continue writing. There are two problems here:
- You need to manually manage the packages that your code uses
- Packages can conflict or malfunction when they are loaded in the wrong order.
Note uses the
packageDep :: Text -> Note
function to declare that a certain package is required.
then makes sure that the right
\usepackage statements end up in
the preamble and that they are in the right order.
Chapters, sections and paragraph
To allow for subpart compilation, I chose the level of chapters, sections
and paragraphs with respect to granularity. Sections are written with the
section :: Text -> Note -> Note function. Code for a
section then looks really simple:
mySection :: Note mySection = section "here be a title" $ do "Here are the contents of my section"
Note will take care of ensuring that the LaTeX code for this
section only gets generated when it is selected for compilation.
Generation of graphics
All the power of Haskell is at the writer’s disposal for the generation of
content, and some parts are made even more convenient. For example, a
Bayesian Network record
:: BayesNet can be drawn with
bnFig $ BayesNet ["A", "B", "C"] [("A", "B"), ("B", "C")]
Similar code works for finite state automata, lattices etc. All you need
to do for this to work is tell Haskell how to turn your data type into a
graph by instantiating the
Note will then take care of
generating the illustration before the LaTeX compilation, caching it for
later re-use and generating the right reference to it to put in the resulting
Note will also make sure that if there are multiple
of these dependencies, they will be resolved in parallel.
When you write a reference in LaTeX with
and there is no corresponding
compilation will succeed as normal, but there will be an ugly “??” in your
pdf where the reference was supposed to be. In the notes, references are made
ref :: Label -> Note and labels with
lab :: Note
Note takes care of making sure that there is a
lab for every corresponding
ref and, unless you use
--ignore-reference-errors, it refuses to compile the document if
there are missing labels.
Code generation with Template Haskell
To write beautiful notes with a lot of internal references and a nice index, a lot of repetitive code is required. I opted to take the grind out of this process by writing some template Haskell that generates the necessary functions to lift the burden from the writer.
When the writer declares a term with
makeDef "pie", Template
Haskell will generate some functions such that
pie :: Note is
now available for literal usage. Using
pie also ensures an entry
in the index at the back of the pdf. Note that spelling errors now become
compile-time errors for these terms.
pie' is the bold version of
pie_ is a literal version that includes a note that references
pie' for easy referencing of terms. The functions
pies' are also generated to be used as
plurals and they take care of referencing and indexing as well.