code in [http://haskell.org Haskell], and hopes to intuitively motivate

+

code in [http://haskell.org Haskell] and hopes to intuitively motivate

and introduce some of the advanced features of Haskell to the novice

and introduce some of the advanced features of Haskell to the novice

programmer. Our goal is to write a concise, robust and elegant

programmer. Our goal is to write a concise, robust and elegant

Line 26:

Line 27:

</haskell>

</haskell>

−

The key here is the <hask>main</hask> function. This is the entry point

+

The key here is the <code>main</code> function. This is the entry point to a Haskell program. We first connect to the server, then set the buffering on the socket off. Once we've got a socket, we can then just read and print any data we receive.

−

to a Haskell program. We first connect to the server, then set the

+

−

buffering on the socket off. Once we've got a socket, we can then just

+

−

read and print any data we receive.

+

−

Put this code in the module <hask>1.hs</hask>, and we can then run it.

+

Put this code in the module <code>1.hs</code> and we can then run it. Use whichever system you like:

−

Use whichever system you like:

+

Using runhaskell:

Using runhaskell:

Line 49:

Line 46:

"NOTICE AUTH :*** Looking up your hostname...\r\nNOTICE AUTH :***

"NOTICE AUTH :*** Looking up your hostname...\r\nNOTICE AUTH :***

Checking ident\r\nNOTICE AUTH :*** Found your hostname\r\n ...

Checking ident\r\nNOTICE AUTH :*** Found your hostname\r\n ...

−

Or using GHCi:

Or using GHCi:

Line 68:

Line 64:

== Talking IRC ==

== Talking IRC ==

−

Now we're listening to the server, we better start sending some

+

Now we're listening to the server, we better start sending some information back. Three details are important: the nick, the user name, and a channel to join. So let's send those.

−

information back. Three details are important: the nick, the user name,

+

−

and a channel to join. So let's send those.

+

<haskell>

<haskell>

Line 95:

Line 89:

printf "> %s %s\n" s t

printf "> %s %s\n" s t

+

listen :: Handle -> IO ()

listen h = forever $ do

listen h = forever $ do

s <- hGetLine h

s <- hGetLine h

putStrLn s

putStrLn s

where

where

−

forever a = a >> forever a

+

forever a = do a; forever a

</haskell>

</haskell>

−

Now, we've done quite a few things here. Firstly, we import

+

Now, we've done quite a few things here. Firstly, we import <code>Text.Printf</code>, which will be useful. We also set up a channel name and bot nickname. The <code>main</code> function has been extended to send messages back to the IRC server using a <code>write</code> function. Let's look at that a bit more closely:

−

<hask>Text.Printf</hask>, which will be useful. We also set up a channel

+

−

name and bot nickname. The <hask>main</hask> function has been extended

+

−

to send messages back to the IRC server, using a <hask>write</hask>

+

−

function. Let's look at that a bit more closely:

+

<haskell>

<haskell>

Line 115:

Line 106:

</haskell>

</haskell>

−

We've given <hask>write</hask> an explicit type to help document it, and

+

We've given <code>write</code> an explicit type to help document it, and we'll use explicit types signatures from now on, as they're just good practice (though of course not required, as Haskell uses type inference to work out the types anyway).

The <code>write</code> function takes 3 arguments; a handle (our socket), and then two strings representing an IRC protocol action, and any arguments it takes. <code>write</code> then uses <code>hPrintf</code> to build an IRC message and write it over the wire to the server. For debugging purposes we also print to standard output the message we send.

−

socket), and then two strings representing an IRC protocol action, and

+

−

any arguments it takes. <hask>write</hask> then uses <hask>hPrintf</hask>

+

−

to build an IRC message, and write it over the wire to the server. For

+

−

debugging purposes we also print to standard output the message we send.

+

−

Our second function, <hask>listen</hask>, is as follows:

+

Our second function, <code>listen</code>, is as follows:

<haskell>

<haskell>

Line 134:

Line 118:

putStrLn s

putStrLn s

where

where

−

forever a = a >> forever a

+

forever a = do a; forever a

</haskell>

</haskell>

−

This function takes a Handle argument, and sits in an infinite loop

+

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, <code>forever</code>, as a normal function! <code>forever</code> 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 <code>do</code>-notation, and directly write: <code>forever a = a >> forever a</code>.

−

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, <hask>forever</hask>,

+

−

as a normal function! <hask>forever</hask> 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,

+

−

when you can just write a normal function to implement whatever control

This is useful for servers that require pings to keep clients connected.

−

keep clients connected. We also add a new function, <hask>eval</hask>,

+

Before we can process a command, remember the IRC protocol generates

−

which takes a cleaned up input string, and then dispatches bot commands

+

input lines of the form:

−

where appropriate:

+

+

<haskell>

+

:dons!i=dons@my.net PRIVMSG #tutbot-testing :!id foo

+

</haskell>

+

+

so we need a <code>clean</code> function to simply drop the leading ':' character, and then everything up to the next ':', leaving just the actual command content. We then pass this cleaned up string to <code>eval</code>, which then dispatches bot commands.

+

<haskell>

<haskell>

eval :: Handle -> String -> IO ()

eval :: Handle -> String -> IO ()

Line 238:

Line 197:

eval _ _ = return () -- ignore everything else

eval _ _ = return () -- ignore everything else

</haskell>

</haskell>

+

+

So, if the single string "!quit" is received, we inform the server and exit the program. If a string beginning with "!id" appears, we echo any argument string back to the server (<code>id</code> is the Haskell identity function, which just returns its argument). Finally, if no other matches occur, we do nothing.

−

So, if the single string "!quit" is received, we inform the server, and

+

We add the <code>privmsg</code> function - a useful wrapper over <code>write</code> for sending <code>PRIVMSG</code> lines to the server.

−

exit the program. If a string beginning with "!id" we echo any argument

+

−

string back to the server (<hask>id</hask> id is the Haskell identity

+

−

function, which just returns its argument). Finally, if no other matches

+

−

occur, we do nothing.

+

−

We add the <hask>privmsg</hask> function, a useful wrapper over

+

<haskell>

−

<hask>write</hask> for sending <hask>PRIVMSG</hask> lines to the server.

+

privmsg :: Handle -> String -> IO ()

+

privmsg h s = write h "PRIVMSG" (chan ++ " :" ++ s)

+

</haskell>

Here's a transcript from our minimal bot running in channel:

Here's a transcript from our minimal bot running in channel:

Line 262:

Line 221:

== Roll your own monad ==

== Roll your own monad ==

−

A small annoyance so far has been that we've had to thread around our

+

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 <em>immutable state</em>, that could be treated as a global read only value in other languages. In Haskell, we can implement such a structure using a state <em>monad</em>. Monads are a very powerful abstraction, and we'll only touch on them here. The interested reader is referred to [http://www.haskell.org/haskellwiki/All_About_Monads All About Monads]. We'll be using a custom monad specifically to implement a read-only global state for our bot.

−

socket to every function that needs to talk to the network. The socket

+

−

is essentially <em>immutable state</em>, that could be treated as a

+

−

global read only value in other languages. In Haskell, we can implement

+

−

such a structure using a state <em>monad</em>. Monads are a very powerful

+

−

abstraction, , and we'll only touch on them here. The interested reader is

+

−

referred to [http://www.nomaware.com/monads/ 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,

+

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.

−

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:

So let's define a small state monad:

Line 284:

Line 232:

</haskell>

</haskell>

−

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

+

Firstly, we define a data type for the global state. In this case, it is the <code>Bot</code> type, a simple struct storing our network socket. We then layer this data type over our existing IO code, with a <em>monad transformer</em>. 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 <code>Net</code> monad. <code>ReaderT</code> is a <em>type constructor</em>, essentially a type function, that takes 2 types as arguments, building a result type: the <code>Net</code> monad type.

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

+

−

<hask>Net</hask> monad.

+

−

We can now throw out all that socket threading, and just grab the socket

+

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 <code>run</code> loop "in" the Net monad, using the Reader monad's <code>runReaderT</code> function:

−

when we need it. The key steps are, once we've connected to the server,

+

−

to initialise our new state monad, and the main bot loop with that

+

−

state. We add a small function, which takes the intial bot state, and

+

−

evaluates the bot's <hask>run</hask> loop "in" the Net monad.

+

<haskell>

<haskell>

Line 302:

Line 240:

</haskell>

</haskell>

−

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

+

where <code>run</code> is a small function to register the bot's nick, join a channel, and start listening for commands.

−

<hask>bracket</hask> to explicitly delimit the connection, shutdown and

+

−

main loop phases of the program -- a useful technique. We can also make

+

While we're here, we can tidy up the main function a little by using <code>Control.Exception.bracket</code> 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 main loop in an exception handler using <code>catch</code>:

−

the code a bit more robust, by wrapping the main loop in an exception

+

−

handler, using <hask>catch</hask>:

+

<haskell>

<haskell>

Line 316:

Line 252:

</haskell>

</haskell>

−

That is, the higher order function <hask>bracket</hask> takes 3

+

That is, the higher order function <code>bracket</code> takes 3 arguments: a function to connect to the server, a function to disconnect and a main loop to run in between. We can use <code>bracket</code> whenever we wish to run some code before and after a particular action - like <code>forever</code>, this is another control structure implemented as a normal Haskell function.

−

arguments: a function to connect to the server, and function to

+

−

disconnect, and a main loop to run in between. We can use

+

−

<hask>bracket</hask> whenever we wish to run some code before and after

+

−

a particular action -- like <hask>forever</hask>, this is another

+

−

control structure implemented as a normal Haskell function.

+

−

Rather than threading the socket around, we can now simply ask for it

+

Rather than threading the socket around, we can now simply ask for it when needed. Note that the type of <code>write</code> changes - it is in the Net monad, which tells us that the bot must already by connected to a server (and thus it is ok to use the socket, as it is initialised).

−

when needed. Note that the type of <hask>write</hask> changes: it is in

+

−

the Net monad, which tells us that the bot must already by connected to

+

−

a server (and thus it is ok to use the socket, as it is initialised).

+

<haskell>

<haskell>

Line 339:

Line 267:

</haskell>

</haskell>

−

In order to use both state and IO, we use the small <hask>io</hask>

+

In order to use both state and IO, we use the small <code>io</code> function to <em>lift</em> an IO expression into the Net monad making that IO function available to code in the <code>Net</code> monad.

−

function to <em>lift</em> an IO expression into the Net monad. This

+

−

tells the compiler.

+

<haskell>

<haskell>

io :: IO a -> Net a

io :: IO a -> Net a

io = liftIO

io = liftIO

+

</haskell>

+

+

Similarly, we can combine IO actions with pure functions by lifting them into the IO monad. We can therefore simplify our <code>hGetLine</code> call:

+

<haskell>

+

do t <- io (hGetLine h)

+

let s = init t

+

</haskell>

+

by lifting <code>init</code> over IO:

+

<haskell>

+

do s <- init `fmap` io (hGetLine h)

</haskell>

</haskell>

Line 355:

Line 291:

import System.IO

import System.IO

import System.Exit

import System.Exit

+

import Control.Arrow

import Control.Monad.Reader

import Control.Monad.Reader

−

import Control.Exception

+

import Control.Exception -- *** for base-3

−

import Control.Concurrent

+

-- import Control.OldException -- *** for base-4

import Text.Printf

import Text.Printf

import Prelude hiding (catch)

import Prelude hiding (catch)

Line 366:

Line 303:

nick = "tutbot"

nick = "tutbot"

−

--

-- The 'Net' monad, a wrapper over IO, carrying the bot's immutable state.

-- The 'Net' monad, a wrapper over IO, carrying the bot's immutable state.

Note that we threw in a new control structure, <hask>notify</hask>, for

+

Note that we threw in a new control structure, <code>notify</code>, for fun. Now we're almost done! Let's run this bot. Using runhaskell:

−

fun. Now we're almost done! Let's run this bot. Using GHC/runhaskell:

+

$ runhaskell 4.hs

$ runhaskell 4.hs

+

+

or using GHC:

$ ghc --make 4.hs -o tutbot

$ ghc --make 4.hs -o tutbot

Line 461:

Line 382:

$ ./tutbot

$ ./tutbot

−

If you're using Hugs, you'll have to use the <hask>-98</hask> flag:

+

If you're using Hugs, you'll have to use the <code>-98</code> flag:

$ runhugs -98 4.hs

$ runhugs -98 4.hs

Line 473:

Line 394:

15:28 -- tutbot [n=tutbot@aa.bb.cc.dd] has quit [Client Quit]

15:28 -- tutbot [n=tutbot@aa.bb.cc.dd] has quit [Client Quit]

−

So we now have a bot with explicit read-only monadic state, error

+

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 <code>ReaderT</code> transformer to <code>StateT</code>.

−

handling, and some basic IRC operations.

+

== Extending the bot ==

== Extending the bot ==

−

Let's implement a basic new command: uptime tracking. Conceptually, we

+

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:

−

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

+

<haskell>

−

do this is to extend the bot's state with a start time field:

+

import System.Time

+

</haskell>

<haskell>

<haskell>

Line 487:

Line 408:

</haskell>

</haskell>

−

We can then modify the initial <hask>connect</hask> function to also set

+

We can then modify the initial <code>connect</code> function to also set the start time.

−

the start time.

+

<haskell>

<haskell>

Line 499:

Line 419:

</haskell>

</haskell>

−

We then add a new case to the <hask>eval</hask> function, to handle

+

We then add a new case to the <code>eval</code> function, to handle uptime requests:

−

uptime requests:

+

<haskell>

<haskell>

−

eval "!uptime" = uptime >>= privmsg

+

eval "!uptime" = uptime >>= privmsg

</haskell>

</haskell>

−

This will just run the <hask>uptime</hask> function, and send it back to

+

This will just run the <code>uptime</code> function and send it back to the server. <code>uptime</code> itself is:

−

the server. <hask>uptime</hask> itself is:

+

<haskell>

<haskell>

Line 517:

Line 435:

</haskell>

</haskell>

−

That is, in the Net monad, find the current time and the start time, and

+

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:

−

then calculate the difference, returning that number as a string.

+

−

Rather than use the normal representation for dates, we'll write our own

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

+

+

Or take the bot home and hack! Some suggestions:

+

* Use <code>forkIO</code> to add a command line interface, and you've got yourself an irc client with 4 more lines of code.

+

* Port some commands from [[Lambdabot]].

+

+

Author: [http://www.cse.unsw.edu.au/~dons Don Stewart]

[[Category:Tutorials]]

[[Category:Tutorials]]

+

[[Category:Code]]

Revision as of 03:17, 13 December 2013

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.

The key here is the main function. This is the entry point to a Haskell program. We first connect to the server, then set the buffering on the socket off. Once we've got a socket, we can then just read and print any data we receive.

Put this code in the module 1.hs and we can then run it. Use whichever system you like:

Now, we've done quite a few things here. Firstly, we import Text.Printf, which will be useful. We also set up a channel name and bot nickname. The main function has been extended to send messages back to the IRC server using a write function. Let's look at that a bit more closely:

We've given write an explicit type to help document it, and we'll use explicit types signatures from now on, as they're just good practice (though of course not required, as Haskell uses type inference to work out the types anyway).

The write function takes 3 arguments; a handle (our socket), and then two strings representing an IRC protocol action, and any arguments it takes. write then uses hPrintf to build an IRC message and write it over the wire to the server. For debugging purposes we also print to standard output the message we send.

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 do-notation, and directly write: forever a = a >> forever a.

We add 3 features to the bot here by modifying listen.
Firstly, it responds to PING messages: if ping s then pong s ... .
This is useful for servers that require pings to keep clients connected.
Before we can process a command, remember the IRC protocol generates
input lines of the form:

:dons!i=dons@my.net PRIVMSG #tutbot-testing :!id foo

so we need a clean function to simply drop the leading ':' character, and then everything up to the next ':', leaving just the actual command content. We then pass this cleaned up string to eval, which then dispatches bot commands.

So, if the single string "!quit" is received, we inform the server and exit the program. If a string beginning with "!id" appears, we echo any argument string back to the server (id is the Haskell identity function, which just returns its argument). Finally, if no other matches occur, we do nothing.

We add the privmsg function - a useful wrapper over write for sending PRIVMSG lines to the server.

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 main loop in an exception handler using catch:

That is, the higher order function bracket takes 3 arguments: a function to connect to the server, a function to disconnect and a main loop to run in between. We can use bracket whenever we wish to run some code before and after a particular action - like forever, this is another control structure implemented as a normal Haskell function.

Rather than threading the socket around, we can now simply ask for it when needed. Note that the type of write changes - it is in the Net monad, which tells us that the bot must already by connected to a server (and thus it is ok to use the socket, as it is initialised).

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:

import System.Time

data Bot = Bot { socket :: Handle, starttime :: ClockTime }

We can then modify the initial connect function to also set the start time.

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: