So given access to a list of all the request parameters and all the cookies, this might be able to get a value (or if the desired cookies or parameters are missing, the result is just Nothing). Now, most of the time I don’t build really complicated things in this monad. Instead, there are the “primitives”

And voila, I’m done. The monad could be helpful, though, if there is a set of fields that often go together. Suppose I have address forms in several places around my web application. Great, I can build this as follows

This is rather convenient for handling form submissions and the like. So now that I’m looking at building an application in Snap, I looked for something similar. Well, it doesn’t seem to exist. Fine, I said to myself, I’ll write it myself.

I noticed from the very beginning (from past experience in Happstack) that I wanted to make at least one change. Namely, I wanted to give my new RqData access to the entire request, including the request headers. I’ve needed this in the past for implementing a replacement for a web service where the way data was stored in the parameters was described by request headers. So a first attempt was this.

That does it. Implementation of equivalent cookie and header primitives is left as an exercise for the reader. (Yes, I’m golfing these functions a bit to turn them into one-liners in the interest of making a point… but they still aren’t all that unreadable.)

An interesting insight, though, is that the Snap monad (and Happstack’s ServerPart monad, too) already acts as a reader monad for the request, and also provides for failure a la the Maybe monad. So we can eliminate a type, at the expense of allowing people to write pathological “RqData” values that modify the response, do I/O, or finish early with a given response. If we are willing to just trust the user not to do these things (or, in the case of I/O, perhaps to know what they are doing and decide to do it anyway — now looking up a code in the database can be part of looking up request data), we can proceed. The simplification looks like this for our venerable withDataFn:

The only difference between look and getParam from the Snap core library is that getParam uses an explicit Maybe, while we want to just pass on handling the request in the Snap monad.

To be fair, this isn’t all there is to the use of RqData in Happstack. There’s also a typeclass, and a default RqData object that can be defined per type. At the same time, though, I see no reason that can’t be done using the ServerPart monad instead of RqData (or, in the Snap world, the Snap monad). The lack of compile-time enforcement that data extraction is free from side effects could also be useful. But in my mind, eliminating a commonly used data type and turning a commonly used function into the (>>=) operator is a simplification worth that cost.

I’m suddenly a lot more comfortable with Snap’s lack of copious numbers of combinators and structures for handling requests. When I can do the same things with fewer operations, that makes me happy.

Back in the early days of HAppS/happstack, routing was done in a very structured manner.

1. use the ‘dir’, ‘path’, etc, combinators to route based on the pathInfo portion of the URL
2. use ‘withDataFn’ to extract the query parameters / form data
3. use ‘method’ to specify which request method you are matching on (GET, POST, etc)
4. generate your Response in the WebT monad

These days that structure is not enforced, except we still had RqData hanging around. Now RqData is around, but optional. You can just do everything in the ServerPart monad. If you want to generate your Response, then look at the query parameters, then check the request method, and finally examine the pathInfo, you can do it that way. ;)

One thing that is a little wonky to implement in ServerPart monad (but not RqData) is the localRqEnv function:

localRqEnv :: (RqEnv -> RqEnv) -> m a -> m a

This is used with (new) filters like ‘body’ and ‘queryString’ which limit the scope of where look searches. But, as a library user, you don’t need to know that it is wonky on the inside :)