Search code examples
haskellmonadshaskell-snap-framework

Avoiding case expressions for chained lookups in Snap


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?


Solution

  • 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.