Archive for the ‘Uncategorized’ Category

Simpler is Better

February 26, 2010

Still double posting from the new blog.

I was in the middle of writing a post about non-recursive enumerators (see my last post) when I realized it was too much of an uphill battle. While they gave the promise of uniting both request and response bodies under a single interface that allowed easy generation of lazy bytestrings, they were going to be too complicated.

So I’ve broken down and admitted that I will have to have two separate interfaces for these things. It makes sense after all: the requirements for a server spitting out a response body are quite different than an application reading a request body.

So I think it’s safe to declare the winner on the response body side to be the recursive enumerator (henceforth known as enumerator). In addition, to deal with a number of compile type issues, I’ve added a newtype, so that the definition of Enumerator is:

newtype Enumerator = Enumerator { runEnumerator :: forall a.
              (a -> B.ByteString -> IO (Either a a))
                 -> a
                 -> IO (Either a a)
}

I know it looks scary, but it’s not really that bad: first argument is an iteratee and the second is the initial seed. Don’t worry, they wai repository has some good examples of how to use it in the Network.Wai.Enumerator module. You can also check out the wai-extra repository.

Without rehashing the discussion from the last post, I mentioned what I called a “Source”, but complained that it required the use of a MVar. I also alluded to an alternate definition that did away with the MVar, instead allowing explicit state passing. That’s what’s currently in the WAI. The definition is:

data Source = forall a. Source a (a -> IO (Maybe (B.ByteString, a)))

The Source constructor has two pieces: the “a” and that ugly function. The “a” is the initial state of the Source. One example (used by both SimpleServer and CGI in wai-extra) for that “a” is the value of the Content-Length header.

The second piece (the ugly function) takes some state, and then gives you the next piece of the request body and a new state, if they’re available. Back to the previous example, if given a value of 0, it knows that the request body has been entirely consumed and returns Nothing. Otherwise, it takes another chunk off the request body, and returns that chunk with the remaining length.

I think this is by far the simplest approach to program to. I was reluctant to introduce it since it involves two different interfaces, but at this point I see no better alternative. If anyone is actually interested in why I’m rejecting non-recursive enumerators, feel free to ask.

At this point, I think the WAI is ready to be released. I’ll wait a week for comments, and then put it on Hackage.

Four HTTP Request Body Interfaces (repost)

February 21, 2010

Looks like Planet Haskell is still linking to my old blog, so here’s a repost from the new blog:

Sorry for the long delay since my last post, I’ve actually been working on a project recently. It should be released last week, but since it’s a web application programmed in Haskell, it’s given me a chance to do some real-world testing of WAI, wai-extra (my collection of handlers and middlewares) and Yesod (my web framework). None of these have been released yet, though that date is fast approaching.

Anyway, back to the topic at hand: request body interface. I’m going to skip over the response body for now because, frankly, I think it’s less contraversial: enumerators seem to be a good fit. What flavor of enumerator could be a good question, but I’d rather figure out what works best on the request side and then choose something that matches nicely.

I’ve been evaluating the choices in order to decide what to use in the WAI. In order to get a good comparison of the options, let’s start off by stating our goals:

  • Performant. The main goal of the WAI is not user-friendliness, but to be the most efficient abstraction over different servers possible.
  • Safe. You’ll see below some examples of being unsafe.
  • Determinstic. We want to make sure that we are never forced to use more than a certain amount of memory.
  • Early termination. We shouldn’t be forced to read the entire contents of a long body, as this could open up DoS attacks.
  • Simple. Although user-friendliness isn’t the first goal, it’s still something to consider.
  • Convertible. In particular, many people will be most comfortable (application-side) using cursors and lazy bytestrings, so we’d like an interface that can be converted to those.

One other point: we’re not going to bother considering anything but bytestrings here. I think the reasons for this are obvious.

Lazy bytestring

This is the approach currently used by Hack, which is pretty well used and accepted by the community (including myself).

Pros

  • Allows both the server and client to write simple code.
  • Lots of tools to support it in standard libraries.
  • Mostly space efficient.

Cons

  • I said mostly space efficient, because you only get the space efficiency if you use lazy I/O. Lazy I/O is also known as unsafeInterleaveIO. Remember that concern about safety I mentioned above? This is it.
  • Besides that, lazy I/O is non-deterministic.

In fact, avoiding lazy I/O is the main impetus for writing the WAI. I don’t consider this a possible solution.

Source

The inspiration for this approach is- frankly- every imperative IO library on the planet. Think of Handle: you have functions to open the handle, close it, test if there’s more data (isEOF) and to get more data. In our case, there’s no need for the first two (the server performs them before and after calling the application, respectively), so we can actually get away with this definition:

type Source = IO (Maybe ByteString) -- a strict bytestring

Each time you call Source, it will return the next bytestring in the request, until the end, where a Nothing is returned.

Pros

  • Simple and standard.
  • Deterministic.
  • Space efficient.

Cons

  • This makes the server the callee, not the caller. In general, it is more difficult to write callees, though in the particular case of a server I’m not certain how much more difficult it really is.
  • This provides no mechanism for the server to keep state (eg, bytes read so far).

Overall, this is a pretty good approach nonetheless. Also, at the cost of complicating things a bit, we could redefine Source as:

type Source a = a -> IO (Maybe (a, ByteString))

This would solve the second of the problems above by forcing the application to thread the state through.

Recursive enumerator

The idea for the recursive enumerator comes from a few sources, but I’ll cite Hyena for the moment. The idea takes a little bit of time to wrap your mind around, and it doesn’t help that there are many definitions of enumerators and iteratees with slightly different definitions. Here I will present a very specialized version of an enumerator, which should hopefully be easier to follow.

You might be wondering: what’s a recursive enumerator? Just ignore the word for now, it will make sense when we discuss the non-recursive variant below.

Anyway, let’s dive right in:

-- Note: this is a strict byte string
type Enumerator a = (a -> ByteString -> IO (Either a a))
                  -> a
                  -> IO (Either a a)

I appologize in advance for having slightly complicated this type from its usual form by making the return type IO (Either a a) instead of IO a, but it has some real world uses. I know it’s possible to achieve the same result with the latter definition, but it’s slightly more work. I’m not opposed to switching back to the former if there’s enough desire.

So what exactly does this mean? An Enumerator is a data producer. When you call the enumerator, it’s going to start handing off one bytestring at a time to the iteratee.

The iteratee is the first argument to the enumerator. It is a data consumer. To put it more directly: the application will be writing an iteratee which receives the raw request body and generates something with it, most likely a list of POST parameters.

So what’s that a? It has a few names: accumulator, seed, or state. That’s the way the iteratee is able to keep track of what it’s doing. Each step along the way, the enumerator will collect the result of the iteratee and pass it in next time around.

And finally, what’s going on with that Either? That’s what allows us to have early termination. If the iteratee returns a Left value, it’s a signal to the enumerator to stop processing data. A Right means to keep going. Similarly, when the enumerator finishes, it returns a Left to indicate that the iteratee requested early termination, and Right to indicate that all input was consumed.

To give a motivating example, here’s a function that converts an enumerator into a lazy bytestring. Two things: firstly, this function is not written efficiently, it’s meant to be easy to follow. More importantly, this lazy bytestring is not exactly lazy: the entire value must be read into memory. If we were two convert this in reality to a lazy bytestring, we would want to use lazy IO so reduce memory footprint. However, as Nicolas Pouillard pointed out to me, the only way to do this involes forkIO.

import Network.Wai
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import Control.Applicative

type Iteratee a = a -> S.ByteString -> IO (Either a a)

toLBS :: Enumerator [S.ByteString] -> IO L.ByteString
toLBS e = L.fromChunks . reverse . either id id <$> e iter [] where
    iter :: Iteratee [S.ByteString]
    iter bs b = return $ Right $ b : bs

As this post is already longer than I’d hoped for, I’ll skip an explanation and to pros/cons:

Pros

  • Space efficient and deterministic.
  • Server is the caller, makes it easier to write.
  • No need for IORef/MVar at all.

Cons

  • Application is the callee, which is more difficult to write. However, this can be mitigated by having a single package which does POST parsing from an enumerator.
  • Cannot be (simply) translated into a source or lazy bytestring. Unless someone can show otherwise, you need to start the enumerator is a separate thread and then use MVars or Chans to pass the information back. On top of that, you then need to be certain to use up all input, or else you will have a permanently locked thread.

While I think this is a great interface for the response body, and I’ve already implemented working code on top of this, I’m beginning to think we should reconsider going this route.

Non-recursive enumerator

The inspiration for this approach comes directly from a paper by Oleg. I found it easier to understand what was going on once I specialized the types Oleg presents, so I will be doing the same here. I will also do a little bit of renaming, so appologies in advance.

The basic distinction between this and a recursive enumerator is that the latter calls itself after calling the iteratee, while the former is given a function to call.

I’m not going to go into a full discussion of this here, but I hope to make another post soon explaining exactly what’s going on (and perhaps deal with some of the cons).

type Enumerator a = RecEnumerator a -> RecEnumerator a
type RecEnumerator a = Iteratee a -> a -> IO (Either a a)
type Iteratee a = a -> B.ByteString -> IO (Either a a)

Pros

  • Allows creation of the source (Oleg calls it a cursor) interface- and thus lazy byte string- without forkIO.
  • Space efficient and deterministic.

Cons

  • I think it’s significantly more complicated than the other approaches, though that could just be the novelty of it.
  • It still requires use of an IORef/MVar to track state. I have an idea of how to implement this without that, but it is significantly more complex.

Conclusion

Well, the conclusion for me is I’m beginning to lean back towards the Source interface. It’s especially tempting to try out the source variant I mention, since that would eliminate the need for IORef/MVar. I’d be interested to hear what others have to say though.

New blog address

January 10, 2010

Hey all,

I’ve got a new blog address at:

http://www.snoyman.com/blog/

If you’ve been reading this from Planet Haskell, I’ve requested of them to update the feed address, so in a few days (hopefully) you’ll be getting all my updates.

data-object family

December 17, 2009

A big thanks to Nicolas Pouillard, who co-authored data-object (as well as some of the underlying libraries like attempt) for coming up with many of the great ideas here.

Introduction

Before you get worried, this has nothing to do with object-oriented. The term “object” here refers to a JSON object, which basically means a data type which can represent three things:

  • Scalars
  • Sequences (or lists)
  • Mappings (or dictionaries)

This format happens to be an incredibly useful things, and the goal of data-object is to provide the Object data type in one place where other libraries can use it, and thus easily exchange data with other libraries. So far, this library has been used for:

  • data-object-json: a wrapper around json-b for JSON parsing/emitting
  • data-object-yaml: a binding to the libyaml C library. (Note: the C source code is included in the package, so you don’t need to have it installed separately on your system.)
  • json2yaml: a simple utility program for converting JSON to YAML files (I was shocked that I couldn’t find something like this elsewhere).
  • It is also playing a prominent role in the Yesod web framework to provide such features as automatic string escaping, JSON output and interfacing with HStringTemplate

Hopefully that gives you an idea that this library is useful. Before rolling your own data type to do basically the same thing, please consider using this library instead.

Overview of design choices

The datatype itself is incredibly simple; the important points are what go along with it.

  • The Object datatype is polymorphic in both the key and value. You can make String->String objects, Int->String, or anything else you like.
  • This library depends on convertible-text, which provides generic conversion type classes.
  • There is a template haskell function included to automatically generate a number of instances.
  • There are three specific aliases provided for Object in their own modules: TextObject, StringObject and ScalarObject.

What, no code samples?

Sorry, not this time. If you want to see example code that uses the data-object library, I recommend data-object-json and json2yaml (data-object-yaml has a lot of C library cruft).

Also, this library is still young, so I’m very much open to suggestions.

Introduction to attempt error reporting library

October 25, 2009

I’ve just released the attempt package on hackage. It is meant to address the issue of error handling, which is currently rather ad-hoc in Haskell. It’s my hope that by putting it in its own package, we can start to standardize between packages and get some nice composable error handling between packages.

The library is built on extensible exceptions to give users the ability to return more complex exception values than is afforded by either a Maybe or (Either String). It’s similar to an (Either SomeException), but provides many class instances, a monad transformer and helper functions.

Below is an HTML version of the literate Haskell example file that is in the attempt repository. Hopefully it will give you a running start on how to use it.

This library should be considered unstable, in that the API is still open to change. As such, I’d appreciate any feedback people have.


This file is an example of how to use the attempt library, as literate
Haskell. We’ll start off with some import statements.

> {-# LANGUAGE DeriveDataTypeable #-}
> {-# LANGUAGE ExistentialQuantification #-}
> import Data.Attempt
> import Control.Monad.Attempt
> import qualified Data.Attempt.Helper as A
> import System.Environment (getArgs)
> import Safe (readMay)
> import Data.Generics
> import qualified Control.Exception as E

We’re going to deal with a very simplistic example. Let’s say you have some
text files that need processing. The files are each three lines long. The
first and last line are integers; the second is a mathematical operator (one
of +, -, * and /). Your goal with each file is to simply perform the
mathematical operator on the two numbers. Let’s start with the Operator data
type.

> data Operator = Add | Sub | Mul | Div
> instance Read Operator where
>   readsPrec _ "+" = [(Add, "")]
>   readsPrec _ "-" = [(Sub, "")]
>   readsPrec _ "*" = [(Mul, "")]
>   readsPrec _ "/" = [(Div, "")]
>   readsPrec _ s = []
>
> toFunc :: Operator -> Int -> Int -> Int
> toFunc Add = (+)
> toFunc Sub = (-)
> toFunc Mul = (*)
> toFunc Div = div

Nothing special here (besides some sloppy programming). Let’s go ahead and
write the first version of our process function.

> process1 :: FilePath -> IO Int
> process1 filePath = do
>   contents <- readFile filePath -- IO may fail for some reason
>   let [num1S, opS, num2S] = lines contents -- maybe there aren't 3 lines?
>       num1 = read num1S -- read might fail
>       op   = read opS   -- read might fail
>       num2 = read num2S -- read might fail
>   return $ toFunc op num1 num2

If you test this function out on a valid file, it works just fine. But what
happens when you call it with invalid data? In fact, there are five things
which could go wrong that I’d be interested in dealing with in the above code.

So now we need some way to deal with these issues. There’s a few standard ones
in the Haskell toolbelt:

  1. Wrap the response in Maybe. Disadvantage: can’t give any indication what he
    error was.
  2. Wrap the response in an Either String. Disadvantage: error type is simply a
    string, which isn’t necesarily very informative. Also, Either is not defined
    by the standard library to be a Monad, making this type of processing clumsy.
  3. Wrap in a more exotic Either SomeException or some such. Disadvantage:
    still not a Monad.
  4. Declare your own error type. Disadvantage: ad-hoc, and makes it very
    difficult to compose different libraries together.

In steps the attempt library. It’s essentially option 4 wrapped in a library
for general consumption. Features include:

  1. Uses extensible exceptions so you can report whatever information you want.
  2. Exceptions are not explicitly typed, so you don’t need to wrap insanely
    long function signatures to explain what exceptions you might be throwing.
  3. Defines all the standard instances you want, including providing a monad
    transformers.

    1. Attempt is a Monad.
    2. There is a Data.Attempt.Helper module which provides a special read
      function.
  4. Let’s transform the above example to use the attempt library in its most basic
    form:

    > data ProcessError = NotThreeLines String | NotInt String | NotOperator String
    >   deriving (Show, Typeable)
    > instance E.Exception ProcessError
    >
    > process2 :: FilePath -> IO (Attempt Int)
    > process2 filePath =
    >   E.handle (\e -> return $ Failure (e :: E.IOException)) $ do
    >       contents <- readFile filePath
    >       return $ case lines contents of
    >           [num1S, opS, num2S] ->
    >               case readMay num1S of
    >                   Just num1 ->
    >                       case readMay opS of
    >                           Just op ->
    >                               case readMay num2S of
    >                                   Just num2 -> Success $ toFunc op num1 num2
    >                                   Nothing -> Failure $ NotInt num2S
    >                           Nothing -> Failure $ NotOperator opS
    >                   Nothing -> Failure $ NotInt num1S
    >           _ -> Failure $ NotThreeLines contents

    If you run these on the sample files in the input directory, you’ll see that
    we’re getting the right result; the program in not erroring out, simply
    returning a failure message. However, this wasn’t very satisfactory with all of
    those nested case statements. Let’s use two facts to our advantage:

    > data ProcessErrorWrapper =
    >   forall e. E.Exception e => BadIntWrapper e
    >   | forall e. E.Exception e => BadOperatorWrapper e
    >   deriving (Typeable)
    > instance Show ProcessErrorWrapper where
    >   show (BadIntWrapper e) = "BadInt: " ++ show e
    >   show (BadOperatorWrapper e) = "BadOperator: " ++ show e
    > instance E.Exception ProcessErrorWrapper
    > process3 :: FilePath -> IO (Attempt Int)
    > process3 filePath =
    >   E.handle (\e -> return $ Failure (e :: E.IOException)) $ do
    >       contents <- readFile filePath
    >       return $ case lines contents of
    >           [num1S, opS, num2S] -> do
    >               num1 <- wrapFailure BadIntWrapper $ A.read num1S
    >               op   <- wrapFailure BadOperatorWrapper $ A.read opS
    >               num2 <- wrapFailure BadIntWrapper $ A.read num2S
    >               return $ toFunc op num1 num2
    >           _ -> Failure $ NotThreeLines contents

    That certainly cleaned stuff up. The special read function works just as you
    would expected: if the read succeeds, it returns a Success value. Otherwise,
    it returns a Failure.

    But what’s going on with that wrapFailure stuff? This is just to clean up the
    output. The read function will return an exception of type “CouldNotRead”,
    which let’s you know that you failed a read attempt, but doesn’t let you know
    what you were trying to read.

    So far, so good. But that “case lines contents” bit is still a little
    annoying. Let’s get rid of it.

    > process4 :: FilePath -> IO (Attempt Int)
    > process4 filePath =
    >   E.handle (\e -> return $ Failure (e :: E.IOException)) $ do
    >       contents <- readFile filePath
    >       return $ do
    >           let contents' = lines contents
    >           [num1S, opS, num2S] <-
    >               A.assert (length contents' == 3)
    >                        contents'
    >                        (NotThreeLines contents)
    >           num1 <- wrapFailure BadIntWrapper $ A.read num1S
    >           op   <- wrapFailure BadOperatorWrapper $ A.read opS
    >           num2 <- wrapFailure BadIntWrapper $ A.read num2S
    >           return $ toFunc op num1 num2

    There’s unfortunately no simple way to catch pattern match fails, but an
    assertion works almost as well. The only thing which is still a bit irksome is
    the whole exception handling business. Let’s be rid of that next.

    > process5 :: FilePath -> AttemptT IO Int
    > process5 filePath = do
    >   contents <- A.readFile filePath
    >   let contents' = lines contents
    >   [num1S, opS, num2S] <-
    >       A.assert (length contents' == 3)
    >                contents'
    >                (NotThreeLines contents)
    >   num1 <- wrapFailure BadIntWrapper $ A.read num1S
    >   op   <- wrapFailure BadOperatorWrapper $ A.read opS
    >   num2 <- wrapFailure BadIntWrapper $ A.read num2S
    >   return $ toFunc op num1 num2

    There’s a built-in readFile function that handles all that handling of error
    garbage for you. If you compare this version of the function to the first, you
    should notice that it’s very similar. You can avoid a lot of the common
    sources of runtime errors by simply replacing unsafe functions (Prelude.read)
    with safe ones (Data.Attempt.Helper.read).

    However, there’s still one other different between process5 and process2-4:
    the return type. process2-4 return (IO (Attempt Int)), while process5 returns
    an (AttemptT IO Int). This is the monad transformer version of Attempt; read
    the documentation for more details. To get back to the same old return type as
    before:

    > process6 :: FilePath -> IO (Attempt Int)
    > process6 = runAttemptT . process5

    Below is a simple main function for testing out these various functions. Try
    them out on the files in the input directory. Also, to simulate an IO error,
    call them on a non-existant file.

    > main = do
    >   args <- getArgs
    >   if length args /= 2
    >       then error "Usage: Example.lhs <process> <file path>"
    >       else return ()
    >   let [processNum, filePath] = args
    >   case processNum of
    >       "1" -> process1 filePath >>= print
    >       "2" -> process2 filePath >>= print
    >       "3" -> process3 filePath >>= print
    >       "4" -> process4 filePath >>= print
    >       "5" -> runAttemptT (process5 filePath) >>= print
    >       "6" -> process6 filePath >>= print
    >       x -> error $ "Invalid process function: " ++ x

Monadic pairs and Kleisli arrows

October 19, 2009

While working on my data-object library, I needed to apply some monadic functions to a tuple, and get back a monaidc tuple. In code:

f :: Monad m => (a -> m b) -> (c -> m d) -> (a, c) -> m (b, d)

The most obvious thing to do is just long-hand it with do notation:

test1 :: Monad m => (a -> m b) -> (c -> m d) -> (a, c) -> m (b, d)
test1 f g (a, c) = do
    b <- f a
    d <- g c
    return (b, d)

But who wants to write that? I got a recommendation instead to try out liftM2. After playing with it a bit, I came out with:

test2 :: Monad m => (a -> m b) -> (c -> m d) -> (a, c) -> m (b, d)
test2 f g (a, c) = uncurry (liftM2 (,)) $ (f *** g) (a, c)

Which is definitely more respectable (though arguably more line noise). After staring at that for a little bit, I realized that there was nothing particularly monadic about this, and could instead be expressed Applicatively:

test3 :: Applicative f => (a -> f b) -> (c -> f d) -> (a, c) -> f (b, d)
test3 f g (a, c) = uncurry (liftA2 (,)) $ (f *** g) (a, c)

Then of course comes the eta-reduction, so you get:

test4 :: Applicative f => (a -> f b) -> (c -> f d) -> (a, c) -> f (b, d)
test4 f g = uncurry (liftA2 (,)) . (f *** g)

Kleisli

I’m sure you noticed the use of *** in test2, test3 and test4. That’s not too surprising; often times we want to use Data.Arrow functions when operating on tuples. As I was staring at the documentation for Data.Arrow, I decided to see what could be done with Kleisli. I came up with:

test5 :: Monad m => (a -> m b) -> (c -> m d) -> (a, c) -> m (b, d)
test5 f g = runKleisli $ Kleisli f *** Kleisli g

This, in my opinion, is much more readable than the above. For those who don’t know, Kleisli allows turning any monadic function into an arrow. Another advantage of this is I can now do things like:

test6 :: Monad m => (a -> m b) -> (a, c) -> m (b, c)
test6 f = runKleisli $ first $ Kleisli f

The downside of test5 versus test4 is that it only works Monadically, not Applicatively. And in case you were wondering, you can’t define a KleisliA type value which will work on all Applicatives. This boils down to where the real extra power of Monads versus Applicatives lies.

All Arrows must be Categorys. One of the functions of a Category is (.), or essentially function composition. The definition for Kleisli monads goes, after unwrapping:

f . g = \b -> g b >>= f

There is no equivalent to this in Applicatives. So sadly, if I want to make my tuple lifting functions work on applicatives, I’m stuck with liftA2.

Embedding files in a binary

July 23, 2009

So this time, I’m asking for community input.

Problem

So, if you’ve been paying attention to the flow of the packages I’ve written, you might see a direction: write a web application, and let it run as a standalone application. However, what do you do about static files (CSS, JavaScript, etc)?

Solution 1: Install static files in a standard location

This is pretty straight forward, and seems to be a well-accepted practice for lots of types of files. For example, I think it’s recommended for Glade files. However, I have always felt a little awkward about it.

Solution 2: Embed the files in the binary

Overall, this has the benefit of allowing the binary to just be moved around without regard for the static files. That’s my main goal in this. It has a side benefit of minimizing file system access, which for my purposes is not that important. On the downside, it makes it more difficult to make changes (eg, a full recompile to change from blue to green).

I see two ways of accomplishing this.

Solution 2a: Code generator

Have a little utility that takes a file, and converts it to a Haskell file containing the file contents. Disadvantage: an extra step in the compile process. Advantage: you can make a dependency from foo-bar.txt to foo-bar_txt.hs so that your build program knows to automatically regenerate the files.

Solution 2b: Template Haskell

Advantage: simplicity. Disadvantage: you’ll need to touch your Haskell file each time you make a static file change.

I’ve implemented a simple version of this. It exports two functions: one for including a single file, one for an entire directory tree. The code is available on Github. I haven’t uploaded it to Hackage. This is my first stab at Template Haskell, so if you see any glaring mistakes please let me know.

Also, if people have any suggestions on which solution is best, or know of a different solution to this issue, please let me know!

Hack sample- chat server

July 1, 2009

Not that you’ll want to replace IRC with this any time soon, but I’ve put together an incredibly simplistic chat server to demonstrate Hack. The code is available in my hack-samples github repo. I’m not going to copy the source code here, but point out a few cool pieces.

In the imports list, I say import qualified Hack.Handler.SimpleServer as Handler to use the SimpleServer handler. Later on, I use Handler.run 3000 to run a simple server on port 3000. If you instead replace SimpleServer with CGI and remove the 3000, you immediately have a CGI application. Of course, all of the MVar concurrency code is unneeded overhead in a CGI application, but you could also use Happstack, FastCGI or any other handler.

Also, I alluded in my previous post to the idea of using currying to initiate some stuff. This code is a perfect example. In my main function, I load up data from a text file, create a Handle to write to, wrap them both in MVars and use that to curry the app function. This way, all of that initialization code only gets called once, no matter how many requests are served. This is a simple approach which works very well in production.

Not much more to say, I think the code speaks for itself!