Literate Haskell with Markdown

Published on 16 October 2021 Hacking

This is a short guide to writing Literate Haskell programs using Markdown.

The source code of this very Web page is a Markdown file with a frontmatter. At the same time, the source code is a Literate Haskell program, i.e. you can compile and run it.

Let’s write a small program.

First, we define a Haskell module:

module Main where

… and then, define a main function:

main :: IO ()
main = putStrLn "Hello World!"

By now, we have implemented a valid Haskell program that is embedded in our Markdown document (the source code). We will define two more functions to demonstrate doctest.

-- | Adds 7 to the given 'Int'.
--
-- >>> add7 35
-- 42
add7 :: Int -> Int
add7 = (+) 7

-- | Divides 42 by the given 'Int'.
--
-- >>> div42 1
-- 42
-- >>> div42 2
-- 21
-- >>> div42 3
-- 14
-- >>> div42 6
-- 7
-- >>> div42 7
-- 6
-- >>> div42 0
-- 0
-- >>> div42 42
-- 1
div42 :: Int -> Int
div42 = div 42

We need to install markdown-unlit, a custom unlit program to extract Haskell code from Markdown files. Once installed, we can compile our program:

$ ghc -pgmLmarkdown-unlit Main.lhs
[1 of 1] Compiling Main             ( Main.lhs, Main.o )
Linking Main ...

This will produce your executable (Main) along with Main.o and Main.hi files. You can run your program:

$ ./Main
Hello World!

We could have run the program directly using runhaskell, too:

$ runhaskell -pgmLmarkdown-unlit Main.lhs
Hello World!

Also, we can produce the Haskell code of interest:

$ markdown-unlit -h label Main.lhs Main.hs

We can study Main.hs or run doctest on it (do not forget to re-generate Main.hs after changing the source code):

$ doctest Main.hs
label:51: failure in expression `div42 0'
expected: 0
 but got: *** Exception: divide by zero
          ^

Examples: 8  Tried: 7  Errors: 0  Failures: 1