8

因为我之前在另一个问题中过于简单化了,所以我想在这里举一个更清楚的例子。

如何处理必须以顺序方式检查某些条件而不嵌套多个案例的情况?对于“顺序方式”,我的意思是获取一个值(例如从标准输入),检查该值是否符合特定条件,并根据结果获取另一个值等等。

例子:

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

5 回答 5

22

当然。这正是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类型对应于它以 a 失败的情况,值left表示Right它成功到达块的末尾。

于 2012-11-06T14:35:05.400 回答
5

不久前,我写了一系列帖子,回顾了我自己对Either&EitherT类型的了解。你可以在这里阅读:http ://watchchrislearn.com/blog/2013/12/01/working-entirely-in-eithert/

我使用该errors包来获得一堆使用 EitherT 的好帮手(left以及right例如返回 and 的提升版本的函数LeftRight

通过将潜在的失败条件提取到它们自己的助手中,您可以使代码的主线完全按顺序读取,而无需使用 case 语句检查结果。

从那篇文章中,您可以看到该runEitherT部分是如何连续工作的,它恰好具有EitherT. 显然,这段代码是相当人为地展示了MaybeT内部的播放方式EitherT。在真正的代码中,它只是你想要讲述的故事,最后有一个Left/ Right

import Control.Error
import Control.Monad.Trans

-- A type for my example functions to pass or fail on.
data Flag = Pass | Error

main :: IO ()
main = do
  putStrLn "Starting to do work:"

  result <- runEitherT $ do
      lift $ putStrLn "Give me the first input please:"
      initialText <- lift getLine
      x <- eitherFailure Error initialText

      lift $ putStrLn "Give me the second input please:"
      secondText <- lift getLine
      y <- eitherFailure Pass (secondText ++ x)

      noteT ("Failed the Maybe: " ++ y) $ maybeFailure Pass y

  case result of
    Left  val -> putStrLn $ "Work Result: Failed\n " ++ val
    Right val -> putStrLn $ "Work Result: Passed\n " ++ val

  putStrLn "Ok, finished. Have a nice day"

eitherFailure :: Monad m => Flag -> String -> EitherT String m String
eitherFailure Pass  val = right $ "-> Passed " ++ val
eitherFailure Error val = left  $ "-> Failed " ++ val

maybeFailure :: Monad m => Flag -> String -> MaybeT m String
maybeFailure Pass  val = just $ "-> Passed maybe " ++ val
maybeFailure Error _   = nothing
于 2013-12-17T19:02:32.397 回答
2

由于您必须在IOmonad 中,因此最好使用IOmonad 的错误处理功能,而不是在IO. 它避免了所有繁重的工作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

在这种情况下,您的错误只是Strings,由IO's抛出fail,作为 a userError。如果你想抛出其他类型,你需要使用throwIO而不是fail.

于 2012-11-06T16:31:28.447 回答
1

在某些时候,该EitherT软件包已被弃用(尽管transformers-either提供了类似的 API)。幸运的是,有一个替代方案EitherT甚至不需要安装单独的包。

标准的 Haskell 安装带有Control.Monad.Trans.Except模块(来自transformers与 GHC 捆绑在一起的包),其行为几乎与EitherT. 生成的代码几乎Gabriella Gonzalez 答案中的代码相同,但使用runExceptT了代替runEitherTthrowE代替left

import Control.Monad.Trans.Class
import Control.Monad.Trans.Except

main = do
    e <- runExceptT $ do
        a <- lift getLine
        case a of
            "hi" -> lift $ putStrLn "hello!"
            _    -> throwE 1
        b <- lift getLine
        case b of
            "how are you?" -> lift $ putStrLn "fine, thanks!"
            _              -> throwE 2
        return "nice conversation"
    case e of
        Left  n   -> putStrLn $ "Error - Code: " ++ show n
        Right str -> putStrLn $ "Success - String: " ++ str

(请注意,上述transformers-either包实际上是一个包装器,ExceptT旨在提供与仍在使用的代码的兼容性EitherT。)

于 2021-11-05T12:11:25.713 回答
0

警告:Haskell 新手回答。

您可以使用 Maybe monad 避免这种楼梯。本章开头的好例子

但是,由于您要返回错误代码,因此您需要与单子 Either 类似的东西(大概有一个)。

基本思想是,一旦出现“Left 1”错误,您将短路任何未来的步骤(因为懒惰评估)。

于 2012-11-06T14:35:04.307 回答