Search code examples
haskellhappstack

How to use maybe monad inside another monad?


I have this code (inside happstack, but could be just the IO monad):

accountHandler conn = do
  sessionId <- optional $ readCookieValue "sessionId"

  case sessionId of
    Nothing -> seeOther ("/" :: String) $ toResponse ()
    Just s  -> do
      result <- loggedInUserId conn s

      case result of
        Just userId -> seeOther ("/account/" ++ unUserId userId) $ toResponse ()
        Nothing -> seeOther ("/" :: String) $ toResponse ()

I want to remove the nested case statements and write something like:

accountHandler conn = do

  let action = do
                sessionId <- optional $ readCookieValue "sessionId"
                userId    <- loggedInUserId conn sessionId

                return $ seeOther ("/account/" ++ userId)

  maybe (seeOther ("/" :: String)) id action $ toResponse ()

... but userId ends up as a type of Maybe String rather than just String. How can I evaluate the nested do block using the maybe monad? (I would also accept a different refactoring that removes the nested cases.)

UPDATE: Below is a generic, though contrived, version of the same problem:

module Main where

getAnswer expected = do
  l <- getLine

  if l == expected
    then return $ Just l
    else return $ Nothing

main = do
  a <- getAnswer "a"

  case a of
    Nothing -> putStrLn "nope"
    Just x -> do
      b <- getAnswer x

      case b of
        Nothing -> putStrLn "nope"
        Just _ -> putStrLn "correct!"

Solution

  • Ok, with your generic example I could do something with Control¸Monad.Transformers. This allows you to create a stack of monads. You can check it out here: http://hackage.haskell.org/package/transformers-0.3.0.0/docs/Control-Monad-Trans-Maybe.html You can apply MaybeT to everything of type IO (Maybe a) and then do all the computations in the inner do block and then check for Nothing at the end.

    module Main where
    import Control.Monad.Trans.Maybe
    
    
    getAnswer expected = MaybeT $ do
           l <- getLine
           if l == expected
           then return $ Just l
           else return $ Nothing
    
    main = do
        y <- runMaybeT $ do a <- getAnswer "a"
                            b <- getAnswer a
                            return b
        case y of Nothing  -> putStrLn "failure"
                  (Just _) -> putStrLn "correct"
    

    Another version using liftIO and the Alternative type class:

    module Main where
    import Control.Monad.Trans.Maybe
    import Control.Monad.IO.Class
    import Control.Applicative
    
    
    getAnswer expected = MaybeT $ do
      l <- getLine
      if l == expected
        then return $ Just l
        else return $ Nothing
    
    main = do
        _ <- runMaybeT $ do a <- getAnswer "a"
                            b <- getAnswer a
                            liftIO $ putStrLn "correct" 
                       <|> do liftIO $ putStrLn "failure"
        return ()
    

    But using many lift operations is not very elegant.