连续monad中优雅的haskell案例/错误处理

时间:2012-11-06 14:16:44

标签: haskell

因为我之前在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"

我知道有更好的方法来编写这样的聊天机器人,它应该只是展示问题的顺序性。正如您所看到的,对于每个嵌套的情况,代码也会缩进更深。

有没有办法更好地构建这样的代码?我正在考虑在一个地方处理“错误”并描述“成功路径”而不分配错误处理。

4 个答案:

答案 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(leftright函数来返回Left和{{的升级版本1}})。

通过将潜在的失败条件提取到自己的帮助程序中,可以使代码的主线完全按顺序读取,而不会检查结果的case语句。

从那篇文章中,你可以看到Right部分是一个连续的工作块,它恰好具有runEitherT的失败机制。显然,这段代码是相当专业的,以显示EitherTMaybeT内部的播放方式。在实际代码中,它只是您想要讲述的故事,最后只有一个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”错误,你就会将任何未来的步骤短路(因为懒惰的评估)。