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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With