Wednesday, October 13, 2010

Is the RqData monad still needed?

cdsmitch recently asked if RqData is really needed in Happstack. The answer is, "no, but it is still useful sometimes."

I can say "no" with certainty because in the darcs version of Happstack, it is already optional.

The new and improved RqData

Functions like look now work in any monad which is an instance of HasRqData:

> look :: (Functor m, Monad m, HasRqData m) => String -> m String

Since there is a HasRqData instance for ServerPart, we effectively have the function:

> look :: String -> ServerPart String

Here is an example of using look with out having to jump through any hoops:

> module Main where
> import Happstack.Server (ServerPart, look, nullConf, simpleHTTP, ok)
> helloPart :: ServerPart String
> helloPart =
> do greeting <- look "greeting"
> noun <- look "noun"
> ok $ greeting ++ ", " ++ noun
> main :: IO ()
> main = simpleHTTP nullConf $ helloPart

Now if we visit http://localhost:8000/?greeting=hello&noun=rqdata, we will get the message hello, rqdata


But why keep RqData around?

Using look in the ServerPart monad is simple. But when it fails, it just calls mzero. That can be very frustrating if you are debugging your forms or debugging calls to your web service API. Instead of an error telling you what parameter was missing, you simply get a generic 404 error.

Using the RqData monad/applicative functor gives you the option to provide detailed error messages when something goes wrong:

> module Main where
> import Control.Applicative ((<$>), (<*>))
> import Happstack.Server (ServerPart, badRequest, nullConf, ok, simpleHTTP)
> import Happstack.Server.RqData (RqData, look, getDataFn)
> helloRq :: RqData (String, String)
> helloRq =
> (,) <$> look "greeting" <*> look "noun"
> helloPart :: ServerPart String
> helloPart =
> do r <- getDataFn helloRq
> case r of
> (Left e) ->
> badRequest $ unlines e
> (Right (greet, noun)) ->
> ok $ greet ++ ", " ++ noun
> main :: IO ()
> main = simpleHTTP nullConf $ helloPart

If you visit http://localhost:8000/?greeting=hello&noun=world, you will get the familiar greeting hello, world.

But if you leave off the query parameters http://localhost:8000/, you will get a list of errors:

Parameter not found: greeting
Parameter not found: noun

This is really nice when you are debugging your code.

Now with more composability!

Since RqData and ServerPart are instances of Applicative and Alternative you can now reuse many functions from those libraries. For example, if a query parameter is optional, you can simply write:

>     do greet <- optional $ look "greeting"

There is also a new combinator checkRq which can be used to validate query parameters, or to convert a query parameter to another type:

> checkRq :: (Monad m, HasRqData m) => m a -> (a -> Either String b) -> m b

If you are curious be sure to check out the Happstack Crash Course where the new RqData module is documented in detail with many working examples.

I would love to hear feedback on the new and improved RqData module, and any suggestions for improvement!

Also, be on the look out for a future blog post about the RqData Arrow. :)


  1. darcs version of Happstack does not contain any traces of HasRqData class as of now.

  2. Oh, hmm. I made sure I did a 'darcs push' before posting this blog. But apparently I forget the darcs record part ;)

    It should be pushed now, sorry about that. The HasRqData class (which used to be called MonadRqData) has been in there for a while, but I finally got around to actually updating all the look functions to use it.