Stepping Stones Towards Literate Programming

A logbook of setting up literate programming for Haskell experiments.

Creating a testbed Haskell project with Stack

To get started, create a new project with Stack:

$ stack new literate-haskell

But this creates a slightly complicated project with many extraneous files.

Ah, it seems that Stack supports templates, and there’s one called simple. Let’s try that.

$ stack new literate-haskell simple

That creates a much simpler project:

$ find .
<uninteresting files omitted>
./LICENSE
./literate-haskell.cabal
./README.md
./Setup.hs
./src
./src/Main.hs
./stack.yaml

Switching to use Literate Haskell is easy:

mv src/Main{,l}hs

Ok, now we can start writing Literate code!

Installing Pandoc

We can write Literate Haskell files in Markdown syntax, and transform the .lhs file with Pandoc into a regular Markdown file that can be processed by Jekyll (via GitHub Pages).

$ brew install pandoc

Now we can tell Pandoc to generate Markdown from “Literate Markdown”:

$ pandoc -f markdown+lhs -t markdown src/Main.lhs

Cool. Let’s add some front matter to make Jekyll happy:

$ head -n 4 src/Main.lhs
---
title: Stepping Stones Towards Literate Programming
layout: default
---

Hmm, it’s not being included in the output:

$ pandoc -f markdown+lhs -t markdown src/Main.lhs | head -n 4
A logbook for setting up [literate
programming](https://en.wikipedia.org/wiki/Literate_programming) for
Haskell experiments.

Standalone Mode

By accident when messing around with Pandoc output templates, I accidentally discovered that --standalone (-s) will include the front matter. Not sure if this is documented somewhere… So:

$ pandoc -s -f markdown+lhs -t markdown src/Main.lhs | head -n 4
---
layout: default
title: Stepping Stones Towards Literate Programming
---

The gas is on. But hang on, code blocks are being annotated:

``` {.sourceCode .literate .haskell}
module Main where
```

and Jekyll doesn’t like that syntax. Can we disable it? Yes, by turning off the fenced_code_attributes extension:

$ pandoc -s -f markdown+lhs -t markdown-fenced_code_attributes src/Main.lhs
<uninteresting bits omitted>
``` sourceCode
module Main where
```

And Kramdown (the GitHub Markdown processor) seems to be ok with this.

Generating Files for Jekyll

For now, we can just write an index.markdown file

$ pandoc -s -f markdown+lhs -t markdown-fenced_code_attributes src/Main.lhs > index.markdown

and Jekyll can show it to us at http://localhost:4000/literate-haskell

Some Oddities

I use <aside> to put things → over there, but Pandoc seems to get confused with the element following an <aside>. From the previous section:

<aside>Discovering this took a fair bit of trawling through Pandoc
documentation and GitHub issues.</aside>

Generating Files for Jekyll
--------------

and the generated output:

<aside>
Discovering this took a fair bit of trawling through Pandoc
documentation and GitHub issues.
</aside>
Generating Files for Jekyll
---------------------------

The blank line after </aside> has been lost, which confuses Kramdown, we end up with

<p>Generating Files for Jekyll
—————————</p>

instead of the expected <h2>

<h2>Generating Files for Jekyll</h2>

Ah, we can interrupt with a comment:

<aside>Discovering this [...]</aside>

<!-- :( -->

Generating Files for Jekyll
--------------

and… er… what?

<aside>
Discovering this [...]
</aside>
``` sourceCode
!-- :( -->
```

Generating Files for Jekyll
---------------------------

Fine, try indenting with a space

 <!-- :( -->

and…

<aside>
Discovering this took a fair bit of trawling through Pandoc
documentation and GitHub issues.
</aside>
<!-- :( -->

Generating Files for Jekyll
---------------------------

👏👏👏

Oh. Next bug is that Pandoc isn’t passing markdown="1" through, it’s just being stripped. I’ll just re-write the previous aside in pure HTML for now, and try to resolve it later.

Finally, Some Haskell

module Main where
main :: IO ()
main = do
  putStrLn "hello world"

and we can run it directly:

$ stack build  --exec literate-haskell
literate-haskell-0.1.0.0: build (exe)
Preprocessing executable 'literate-haskell' for literate-haskell-0.1.0.0..
Building executable 'literate-haskell' for literate-haskell-0.1.0.0..
[1 of 1] Compiling Main             (
  src/Main.lhs,
  .stack-work/dist/x86_64-osx/Cabal-2.0.1.0/build/literate-haskell/literate-haskell-tmp/Main.o
)
Linking .stack-work/dist/x86_64-osx/Cabal-2.0.1.0/build/literate-haskell/literate-haskell ...
literate-haskell-0.1.0.0: copy/register
Installing executable literate-haskell in
  /<snip>/literate-haskell/.stack-work/install/x86_64-osx/lts-11.8/8.2.2/bin
hello world

👏👏👏

!-- test... -->

Sigh.

Automating It

Let’s use a Makefile to generate the Jekyll post:

../_posts/2018-05-09-literating-haskell.markdown: src/Main.lhs
    pandoc -s -f markdown+lhs -t markdown-fenced_code_attributes src/Main.lhs \
 > ../_posts/2018-05-09-literating-haskell.markdown

But can’t we reuse the target name in the command? Ah, $@, bingo!

../_posts/2018-05-09-literating-haskell.markdown: src/Main.lhs
    pandoc -s -f markdown+lhs -t markdown-fenced_code_attributes src/Main.lhs > $@

And so,

$ make
pandoc -s -f markdown+lhs -t markdown-fenced_code_attributes src/Main.lhs >
  ../_posts/2018-05-09-literating-haskell.markdown
$ make
make: `../_posts/2018-05-09-literating-haskell.markdown' is up to date.

👏👏👏, I guess?