Contents

1 Introduction

I've used Haskell to create various command-line utitities for unix-like
systems. In the process I developed a simple yet powerful and flexible
technique for processing program options.

Sven Panne's GetOpt module does a fine job at parsing command line options. It
has a simple, easy to use interface, handles short and long options,
abbreviated options and most needed option argument modes (no / optional /
required argument). It can even produce a nice usage info from option
descriptions.

However, I checked over half a dozen serious programs written in Haskell and
saw that in most of them the code responsible for option handling is quite
ugly, repetitious and error prone (of course there were exceptions, see below).
Saying 'option handling' I don't mean the work of analyzing the command line,
but rather how supplied options influence program's behaviour, how error
situations are handled, etc.

(The exception is Wolfgang Thaller's VOP program. Wolfgang used a nice, but a
little lengthy technique there. There are probably other ,,exceptional
programs out there).

I think there are two major reasons of the current situation. The first reason
is that option handling is rarely of primary concern to the programmer. Often
there are not that much options to handle in the beginning and it seems that
the simplest solution will do. Alas, if the program is evolving and its users
ask for new functionality, new options appear, and the initial design starts to
be an obstacle.

The other reason is lack of good examples. The example delivered with GetOpt
shows how to use the library, but it doesn't show that there are other, better
ways to use it. I don't propose to introduce more advanced techniques to this
example, because it would make it rather heavy. But it would be nice if there
would be a pointer for interested users.

2 About typical use of GetOpt

In a typical use of GetOpt module (like in the example) there is a definition
of a sum datatype with data constructors corresponding to individual command-line options. Options with no arguments are represented by nullary constructors, and options that must or can have arguments - by unary
constructors. For example, below is a slightly modified example from GetOpt's
documentation:

Because every constructor of Flag datatype has one of types Flag, (String ->
Flag) or (Maybe String -> Flag), they can be given verbatim as first arguments
to appropriate constructors of 'ArgDescr Flag' datatype.

Used in this way, getOpt parses a list of strings to a list of Flag values.
It also separates options from non option arguments, signals unrecoginized,
ambiguous or improperly used options.

But after all, it is not that big step. There is still much processing of this
Flag list to do. One has to check if specific options are there in the
list, some options have to be combined, etc, etc.

3 Advertised technique

3.1 Imports for the full program

( Because I wanted it to be a fully functional Literate Haskell program, here come the required imports )

3.2 Analysis of options typing

Let's look at program options not from the command-line side, but rather from
program(mer)'s side. How to encode them? What would be the best way to present
them to the programmer. How should they influence program's behaviour?

'Verbose' could be an easily accessible Bool value, False by default.
'Version', if supplied, could just print the program's version and exit.
'Input' could just yield a String, either from stdin or from a specified file.
'Output' could just take a String and do something with it.

We need some kind of a fixed-size polytypic dictionary of option values - a
perfect application for Haskell's records.

3.3 Threading the options through the program

But in the end we would like to get the record reflecting the options given to
the program. We do this by threading the Options record through functions
processing single options. Each such function can change this record. Why not
just put such functions in ArgDescr and OptDescr datatypes? Here we benefit
from first-class citizenship of functions.

I won't use a pure function with type (Options -> Options), but rather an
effectful function in the IO Monad, because I want to easily perform side
effects during option parsing (here only in 'version' and 'help' options, but I
could also check validity of input and output files during option processing).
You may prefer to use a pure function, a State+Error monad, a State+IO monad,
or something different...