Literate Haskell with Markdown

Published on 16-10-2021

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

The source code of this very Webpage is a Markdown file with a preamble. At this same time, the source code is also a Literate Haskell program, ie. you can compile and run it. Therefore, we first define a Haskell module:

module Main where

... and then, define a main function:

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

By now, we have a valid Haskell program embedded in our Markdown document (the source code). But 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 that is 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 ran 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 you change 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