I am toying around with the Snap framework and I often encounter the case where I do a database lookup based on a parameter I get from a form field.
Consider e.g. the following two functions
getParam :: (MonadSnap m) => ByteString -> m (Maybe ByteString)
doLookup :: (MonadIO (m b v), MonadSnaplet m, MonadState s (m b b), HasAcid s UrlDB) => ByteString -> m b v (EventResult QueryByURL)
where UrlDB is a mapping between Integers and URLs. The complicated type signature of the second
function is due to the use of acid-state and eventually results in Maybe Integer
.
queryByURL :: Text -> Query UrlDB (Maybe Integer)
So far, my handler looks like
indexHandler :: Handler MyApp MyApp ()
indexHandler = do
mUrl <- getParam "url"
case mUrl of
Nothing -> render "index"
Just url -> do
mId <- doLookup $ url
case mId of
Nothing -> render "index"
Just i -> do
fancyStuffWith i
render "index"
Now, the first thing that annoys me is the staircasing of the case expressions.
The second thing is the threefold appearance of render "index"
.
Basically, whenever one of the two Maybe values is Nothing, I want to return
a default view.
What would be the cleanest way to do this?
This is what the MaybeT monad transformer is for. Your code could be written like this.
indexHandler :: Handler MyApp MyApp ()
indexHandler = do
runMaybeT $ do
url <- MaybeT $ getParam "url"
i <- MaybeT $ doLookup url
fancyStuffWith i
render "index"
The errors package pulls together these things and adds a lot of convenience functions for working with them. In addition to MaybeT, the EitherT monad transformer does something similar but keeps track of error messages so you can track when your computation failed.