[Haskell-cafe] Literal programming in Haskell with rst-literals

Martin Blais blais at furius.ca
Sat Jun 21 14:18:54 EDT 2008


Hello Haskell community!

I just did a marginally cool thing and I wanted to share it
with you.

"rst-literals" is a small program I wrote a while ago in
order to write documents in reStructuredText format that
would embed SQL code for data models in them, a form of
literal programming for SQL if you will; I would describe my
needs for the schema in prose, and reST literal-blocks were
used to embed SQL code, blocks that look like this::

  CLASS Employee (
     firstname VARCHAR,
     lastname VARCHAR
  )

I wrote the script to be entirely generic: it parses the
reST documents using the docutils code and outputs only the
literal-blocks, with indentation removed; you can then run
your favourite interpreter/compiler on the result (in that
case, psql to initialize a database).

Recently, while experimenting with Haskell, I started using
both the literal (.lhs) and non-literal (.hs) styles of
Haskell input, and I found the literal style a bit
unpleasant to use, in particular, I don't like to have to
prefix every line of code I write, despite the help that
Emacs' haskell-mode provides.

So I tried pulling a similar trick and embedding Haskell
code in literal-blocks within reST documents, extracting
that code using rst-literals, and it turns out that it works
like a charm. Here is an example makefile for doing this::

  .SUFFIXES: .rst .hs

  all: chap6

  .rst.hs:
          rst-literals $< > $@

  chap6: chap6.hs
          ghc --make chap6.hs

An example reST document with some embedded Haskell code
follows this email. Note that since rst-literals is using
the docutils parser, you can make use of all of the
recursive reST syntax, sections, bulleted items and much
more. Only the literal-blocks are extracted, anywhere they
appear. You can also easily process the reST source into
HTML pages or LaTeX documents using the tools that come with
docutils.

You can find rst-literals here:
http://furius.ca/pubcode/

Enjoy,




--
Martin

P.S. If there is a way to output cpp-like directives for
GHC, like "#line <filename> <lineno>", it would be easy to
modify rst-literals to generate those, so that compilation
errors could refer to the source reST document instead of
the extracted source.



chap6.hs:
----------------------------------------------------------------------

===========================================
   Exercises from Hutton book, Chapter 6
===========================================

.. contents::
..
    1  Introduction
    2  Exercise 1
    3  Exercise 2
    4  Exercise 3
    5  Exercise 4
    6  Exercise 5
    7  Exercise 6


Introduction
============

Bla bla bla blablablablablabla bla bla blabla. Bla bla bla
blablablablablabla bla bla blabla. Bla bla bla blablablablablabla bla
bla blabla. Bla bla bla blablablablablabla bla bla blabla. Bla bla bla
blablablablablabla bla bla blabla. Bla bla bla blablablablablabla bla
bla blabla.


Exercise 1
==========
::

  myexp :: Int -> Int -> Int
  myexp b 0 = 1
  myexp b (n+1) = b * (myexp b n)


Exercise 2
==========

(Exercise 2 consisted in derivations, so we mark the literal
blocks as another type of block with "#!example", so that
they don't get included in the output when only the
"default" literal blocks get extracted. See rst-literals
docstring for details.)

Length::

    #!example
    1 + (length [2, 3])
    1 + 1 + (length [3])
    1 + 1 + (1)
    3

Drop::

  #!example
  drop 3 [1, 2, 3, 4, 5]
  [] ++ drop 3 [2, 3, 4, 5]
  [] ++ [] ++ drop 3 [3, 4, 5]
  [] ++ [] ++ [] ++ [4, 5]
  [4, 5]

Init::

  #!example
  init [1, 2, 3]
  [1] ++ init [2, 3]
  [1] ++ [2] ++ init [3]
  [1] ++ [2] ++ []
  [1, 2]


Exercise 3
==========

These are alternate versions of the example functions defined in the
text::

  and' :: [Bool] -> Bool
  and' [x] = x
  and' (x:xs) = x && and' xs

  concat' :: [[a]] -> [a]
  concat' [] = []
  concat' (x:xs) = x ++ concat' xs

  replicate' :: Int -> a -> [a]
  replicate' 0 x = []
  replicate' (n+1) x = (x : replicate' n x)

  select' :: [a] -> Int -> a
  select' (x:xs) 0 = x
  select' (x:xs) (n+1) = select' xs n

  elem' :: Eq a => a -> [a] -> Bool
  elem' _ [] = False
  elem' y (x:xs) | x == y = True
                 | otherwise = elem' y xs

Exercise 4
==========

The exercise asked to implement a function to merge two lists::

  merge :: Ord a => [a] -> [a] -> [a]
  merge xs [] = xs
  merge [] xs = xs
  merge (x:xs) (y:ys) | x < y = (x : merge xs (y:ys))
                      | otherwise = (y : merge (x:xs) ys)


Exercise 5
==========
::

  msort :: Ord a => [a] -> [a]
  msort [] = []
  msort [x] = [x] -- This is necessary to end the recursion.
  msort xs = merge (msort (fst hh)) (msort (snd hh))
             where hh = halve xs

  halve :: [a] -> ([a], [a])
  halve xs = (take n xs, drop n xs)
      where n = (length xs) `div` 2

Some main program::

  main = (putStrLn . show) (halve [1..17])


Exercise 6
==========

(Too basic, I didn't bother.)

----------------------------------------------------------------------


More information about the Haskell-Cafe mailing list