我有这个代码(在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!"
答案 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"
使用liftIO
和Alternative
类型的另一个版本:
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
实例的全部功能。例如,如果您需要检查某些条件是否成立,请使用guard
或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
它的类型非常通用,适用于任何MonadPlus
和MonadIO
的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!"