Since writing my set of function combinators for matching HTTP requests I’ve realised that there are better approaches.
While the combinators read nicely, once you start extracting attributes from the request things become messy, due to the
hAnd combinator composing results as nested pairs.
Here’s an example:
skitchPutHandler = let storeImage p = let (p2, path) = p; (maybeContentType,imageData) = p2; ... in ... in matchMethod "PUT" `hThen` matchUrl "/skitch" `hThen` (getHeader "Content-Type") `hAnd` requireRawData `hAnd` requirePathInfo `hApply` storeImage;
Unpacking the values extracted from the request is cumbersome, and because many are
Strings, error prone.
A better approach is to simply use the
Maybe monad with the same extraction functions:
simpleSkitchPutHandler req = let maybeContentType = requireHeader "Content-Type" req; ... in matchMethod "PUT" req `anonymousBind` matchUrl "/skitch" req `anonymousBind` requireRawData req `bind` (\rawData -> requirePathInfo req `bind` (\path -> return $ storeImage path rawData (actualContentType path)));
Optional values are extracted in let expressions, and required values and tests are composed with the monadic
bind operators in the main expression.
You can make the function a bit more concise and ‘point free’ by writing a monad which composes the
Reader monads to supply the HTTP request parameter to functions implicitly:
readerMonadSkitchPutHandler = let ... in runHttpRequestM ( method "PUT" `anonymousBind` matchUrlM "/skitch" `anonymousBind` rawData `bind` (\rawData -> pathInfo `bind` (\path -> (optional (header "Content-Type")) `bind` (\maybeContentType -> return $ storeImage path rawData (actualContentType path maybeContentType)))));
optional has the type
HttpRequestM a -> HttpRequestM (Maybe a), that is, it allows a function to succeed by wrapping its result in a ‘Just’.
I’m not convinced that the improvement in readability gained by removing the request parameter is worth the increase in complexity.
So I’ll remove the combinators from my
Http module and will rewrite Hangman using just the Maybe monad. I’ll keep the more complicated Monad on the back-burner in case I discover a compelling advantage.
I’d be interested to know what someone with a deeper understanding of functional programming would say.