Question

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?

Was it helpful?

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.

OTHER TIPS

To avoid repeating render "index", you have to see that you basically call it at the end of all code pathes. Then you can try to abstract that pattern matching part with a function. The result is not perfect but slightly better.

indexHandler :: Handler MyApp MyApp ()
indexHandler = do
    withJust $ getParam "url" $ \url ->
        withJust $ doLookup url $ fancyStuffWith
    render "index"
  where
    withJust :: (IO (Maybe a)) -> (a -> IO()) -> IO ()
    withJust iomval cmd = do
        mval <- iomval
        case mval of
            Nothing -> return ()
            Just val -> cmd val

the withJust function performs an IO action which might fail to bring a value. If it succeeds, the value is passed to another command.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top