8

我有这段代码(在 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. 如何do使用 Maybe monad 评估嵌套块?(我也会接受一种不同的重构来删除嵌套案例。)

更新:以下是同一问题的通用版本,虽然是人为的:

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

2 回答 2

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) ,然后进行所有计算在内部 do 块中,然后在最后检查 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 ()

但是使用许多电梯操作并不是很优雅。

于 2013-11-29T21:11:27.810 回答
3

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

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

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

为了从您的内部计算中提取MaybeT IO值,我建议编写fromMaybefor的变体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!"
于 2013-12-01T18:17:09.473 回答