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.
Requirements
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.
Haskell instead.
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.
Writer experience
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
. Note
is a Monad that takes care of all the required features while providing a clean interface to write my notes. Note
is also an instance of IsString
such that I can write literal strings and they will end up in the notes as expected.
LaTeX packages
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. Note
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
= section "here be a title" $ do
mySection "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")] bnFig
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 Graph
Typeclass. 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 LaTeX code. Note
will also make sure that if there are multiple of these dependencies, they will be resolved in parallel.
Safe references
When you write a reference in LaTeX with \ref{<foo>}
, and there is no corresponding \label{<foo>}
, the 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 with ref :: Label -> Note
and labels with lab :: Note -> 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
. pie_
is a literal version that includes a note that references pie'
for easy referencing of terms. The functions pies
and pies'
are also generated to be used as plurals and they take care of referencing and indexing as well.