* A gallery of [[Libraries_and_tools/Network|network apps]] in Haskell

* A gallery of [[Libraries_and_tools/Network|network apps]] in Haskell

Revision as of 07:43, 9 December 2007

This tutorial is designed as a practical guide to writing real world
code in Haskell and hopes to intuitively motivate
and introduce some of the advanced features of Haskell to the novice
programmer. Our goal is to write a concise, robust and elegant
IRC bot in Haskell.

Contents

1 Getting started

You'll need a reasonably recent version of GHC
or Hugs. Our first step is to get on the
network. So let's start by importing the Network package, and the
standard IO library and defining a server to connect to.

This function takes a Handle argument, and sits in an infinite loop
reading lines of text from the network and printing them. We take
advantage of two powerful features; lazy evaluation and higher order

functions to roll our own loop control structure,

forever

,
as a normal function!

forever

takes a chunk of code as an

argument, evaluates it and recurses - an infinite loop function. It
is very common to roll our own control structures in Haskell this way,
using higher order functions. No need to add new syntax to the language, lisp-like macros or meta programming - you just write a normal
function to implement whatever control flow you wish. We can also avoid

4 Roll your own monad

A small annoyance so far has been that we've had to thread around our
socket to every function that needs to talk to the network. The socket
is essentially immutable state, that could be treated as a
global read only value in other languages. In Haskell, we can implement
such a structure using a state monad. Monads are a very powerful
abstraction, and we'll only touch on them here. The interested reader is
referred to All About Monads. We'll be
using a custom monad specifically to implement a read-only global state
for our bot.

The key requirement is that we wish to be able to perform IO actions,
as well as thread a small state value transparently through the program.
As this is Haskell, we can take the extra step of partitioning our
stateful code from all other program code, using a new type.

So let's define a small state monad:

data Bot = Bot { socket :: Handle }type Net = ReaderT Bot IO

Firstly, we define a data type for the global state. In this case, it is

the

Bot

type, a simple struct storing our network socket.

We then layer this data type over our existing IO code, with a monad
transformer. This isn't as scary as it sounds and the effect is
that we can just treat the socket as a global read-only value anywhere
we need it. We'll call this new io + state structure the

Net

monad.

ReaderT

is a type

constructor

, essentially a type function, that takes 2 types as
arguments, building a result type: the

Net

monad type.

We can now throw out all that socket threading and just grab the socket
when we need it. The key steps are connecting to the server, followed by
the initialisation of our new state monad and then to run the main bot loop
with that state. We add a small function, which takes the intial bot

state and evaluates the bot's

run

loop "in" the Net monad,
using the Reader monad's

runReaderT

function:

loop st = runReaderT run st

where

run

is a small function to register the bot's nick,

join a channel, and start listening for commands.

While we're here, we can tidy up the main function a little by using

Control.Exception.bracket

to explicitly delimit the

connection, shutdown and main loop phases of the program - a useful
technique. We can also make the code a bit more robust by wrapping the

So we now have a bot with explicit read-only monadic state, error
handling, and some basic IRC operations. If we wished to add read-write

state, we need only change the

ReaderT

transformer to

StateT

.

5 Extending the bot

Let's implement a basic new command: uptime tracking. Conceptually, we
need to remember the time the bot starts. Then, if a user requests, we
work out the total running time and print it as a string. A nice way to
do this is to extend the bot's state with a start time field:

That is, in the Net monad, find the current time and the start time, and
then calculate the difference, returning that number as a string.
Rather than use the normal representation for dates, we'll write our own
custom formatter for dates:

6 Where to now?

This is just a flavour of application programming in Haskell, and only
hints at the power of Haskell's lazy evaluation, static typing, monadic
effects and higher order functions. There is much, much more to be said
on these topics. Some places to start: