如何在另一个monad中使用monad?

时间:2013-11-29 20:35:07

标签: haskell happstack

我有这个代码(在happstack中,但可能只是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 ()

我想删除嵌套的case语句,并编写如下内容:

accountHandler conn = do

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

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

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

...但是userId最终只是Maybe String的一种,而不仅仅是String。如何使用monad来评估嵌套的do块? (我也会接受一个不同的重构来删除嵌套的案例。)

更新:以下是同一问题的通用但虽然设计的版本:

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!"

2 个答案:

答案 0 :(得分:5)

好的,通过您的通用示例,我可以使用Control¸Monad.Transformers执行某些操作。这允许您创建一组monad。你可以在这里查看:http://hackage.haskell.org/package/transformers-0.3.0.0/docs/Control-Monad-Trans-Maybe.html 您可以将MaybeT应用于IO (Maybe a)类型的所有内容,然后在内部执行块中执行所有计算,然后在最后检查Nothing。

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"

使用liftIOAlternative类型的另一个版本:

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 ()

但使用许多升降机操作并不是很优雅。

答案 1 :(得分:3)

我想补充MoFu的答案,一旦你拥有MaybeT IO,就可以使用其MonadPlus实例的全部功能。例如,如果您需要检查某些条件是否成立,请使用guardmfilter。所以你可以写:

import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans
import Control.Monad.Trans.Maybe

getAnswer :: (MonadPlus m, MonadIO m) => String -> m String
getAnswer expected = mfilter (== expected) $ liftIO getLine

它的类型非常通用,适用于任何MonadPlusMonadIO的monad。如果您决定稍后修改monad堆栈,这很方便。但我们也可以使用更具体的类型(MonadIO m) => String -> MaybeT m String

为了从内部计算中提取MaybeT IO值,我建议为fromMaybe编写MaybeT的变体:

fromMaybeT :: (Monad m) => m a -> MaybeT m a -> m a
fromMaybeT onFail = maybe onFail return <=< runMaybeT

它使用runMaybeT提取结果。如果是Just,则只需退回,否则运行onFail操作。

结合在一起,我们得到:

main = fromMaybeT (putStrLn "nope") $ do
  a <- getAnswer "a"
  b <- getAnswer a
  liftIO $ putStrLn "correct!"