因为我之前在other question过度简化了,所以我想在此提供一个更清晰的例子。
如何处理我必须以顺序方式检查certian条件而不嵌套多个案例的情况?使用“顺序方式”,我的意思是获取一个值(例如来自stdin),检查该值是否存在某个条件,并取决于获得另一个值的结果等等。
示例:
sequen :: IO String
sequen = do
a <- getLine
case a of
"hi" -> do
putStrLn "hello!"
b <- getLine
case b of
"how are you?" -> do
putStrLn "fine, thanks"
return "nice conversation"
_ -> return "error 2"
_ -> return "error 1"
我知道有更好的方法来编写这样的聊天机器人,它应该只是展示问题的顺序性。正如您所看到的,对于每个嵌套的情况,代码也会缩进更深。
有没有办法更好地构建这样的代码?我正在考虑在一个地方处理“错误”并描述“成功路径”而不分配错误处理。
答案 0 :(得分:21)
当然。这正是EitherT
的目的。您可以从Control.Monad.Trans.Either
包中的eitherT
获取。
import Control.Monad.Trans.Class
import Control.Monad.Trans.Either
main = do
e <- runEitherT $ do
a <- lift getLine
case a of
"hi" -> lift $ putStrLn "hello!"
_ -> left 1
b <- lift getLine
case b of
"how are you?" -> lift $ putStrLn "fine, thanks!"
_ -> left 2
return "nice conversation"
case e of
Left n -> putStrLn $ "Error - Code: " ++ show n
Right str -> putStrLn $ "Success - String: " ++ str
EitherT
会在遇到left
语句时中止当前代码块,人们通常会使用它来指示错误情况。
内部区块的类型为EitherT Int IO String
。当您runEitherT
时,您会获得IO (Either Int String)
。 Left
类型对应于left
失败的情况,Right
值表示它已成功到达块的末尾。
答案 1 :(得分:5)
我回过头来了解Either
&amp; EitherT
种类型。您可以在此处阅读:http://watchchrislearn.com/blog/2013/12/01/working-entirely-in-eithert/
我使用errors
包来获得一堆好帮手,例如使用EitherT(left
和right
函数来返回Left
和{{的升级版本1}})。
通过将潜在的失败条件提取到自己的帮助程序中,可以使代码的主线完全按顺序读取,而不会检查结果的case语句。
从那篇文章中,你可以看到Right
部分是一个连续的工作块,它恰好具有runEitherT
的失败机制。显然,这段代码是相当专业的,以显示EitherT
在MaybeT
内部的播放方式。在实际代码中,它只是您想要讲述的故事,最后只有一个EitherT
/ Left
。
Right
答案 2 :(得分:1)
由于您必须使用IO
monad,因此最好使用IO
monad的错误处理功能,而不是在IO
之上堆叠错误monad。它避免了所有繁重的lift
:
import Control.Monad ( unless )
import Control.Exception ( catch )
import Prelude hiding ( catch )
import System.IO.Error ( ioeGetErrorString )
main' = do
a <- getLine
unless (a == "hi") $ fail "error 1"
putStrLn "hello!"
b <- getLine
unless (b == "how are you?") $ fail "error 2"
putStrLn "fine, thanks"
return "nice conversation"
main = catch main' $ return . ioeGetErrorString
在这种情况下,您的错误只是String
s,由IO
的{{1}}引发,fail
。如果您想抛出其他类型,则需要使用userError
代替throwIO
。
答案 3 :(得分:0)
警告:哈斯克尔新手回答。
你可以避免使用Maybe monad进行这种楼梯。 this chapter
开头的好例子但是,你需要类似于monadic Either(可能有一个)的东西,因为你要返回错误代码。
基本的想法是,一旦你遇到“左1”错误,你就会将任何未来的步骤短路(因为懒惰的评估)。