【问题标题】:How to avoid pyramid of cases?如何避免案例金字塔?
【发布时间】:2016-10-14 06:04:44
【问题描述】:

我的代码结构类似于下面的示例。我很确定应该有一种方法可以更合理地构建它。我会假设 Either (or Error) monad 可以提供帮助,但我不知道从哪里开始。有什么建议可以让我朝着正确的方向前进吗?

data Data1 = Data1 { d2Id :: String }
data Data2 = Data2 { d3Id :: String }
data Data3 = Data3 { d4Id :: String }

getData1 :: String -> IO (Either String Data1)
getData2 :: String -> IO (Either String Data2)
getData3 :: String -> IO (Either String Data3)

process :: Data1 -> Data2 -> Data3 -> IO ()

get :: String -> IO ()
get id = do
  r1 <- getData1 id
  case r1 of
    Left err -> print err
    Right d1 -> do
      r2 <- getData2 $ d2Id d1
      case r2 of
        Left err -> print err
        Right d2 -> do
          r3 <- getData3 $ d3Id d2
          case r3 of
            Left err -> print err
            Right d3 -> do
              process d1 d2 d3

【问题讨论】:

    标签: haskell either


    【解决方案1】:

    我重新提出这个问题是因为我认为这会有所帮助 如何转换这种特定的代码。

    我们需要一些导入:

    import Control.Monad.Trans
    import Control.Monad.Trans.Either
    

    然后通过将EitherT 应用于每个通过返回Either 来表示错误的IO 操作来转换您的get 函数:

    -- get' :: EitherT String IO ()
    get' id = do
      d1 <- EitherT $ getData1 id
      d2 <- EitherT $ getData2 (d2Id d1)
      d3 <- EitherT $ getData3 (d3Id d2)
      liftIO $ process d1 d2 d3
    

    请注意,我们不会将EitherT 用于process。相反,我们使用liftIO,因为process 不会发出错误信号。

    GHC 应该能够推断类型签名,因此您无需提供它。

    要运行新版本,请使用 runEitherT,它将在 IO-monad 中返回 Either 值:

    doit :: String -> IO ()
    doit id = do
      res <- runEitherT (get' id)
      case res of
        Left err -> print err
        Right d  -> return ()
    

    【讨论】:

    • 问题为何被关闭?
    • 它被标记为重复。这种问题以前也出现过,但不完全是在这种情况下,所以我决定重新打开它并添加一个答案。
    • 似乎 EitherT 不在 Stackage 上,这是否意味着首选方法是使用 except for such control flow? hoogle.haskell.org/?hoogle=EitherT
    【解决方案2】:

    由于Either-5 已放弃对runEitherT 的支持,我建议使用Data.Either.Combinators 中的whenLeftwhenRight。你的代码可以改写如下:

    get :: String -> IO ()
    get id = do
               r1 <- getData1 id
               whenLeft r1 print
               whenRight r1 (\d1 -> do 
                                      r2 <- getData2 $ d2Id d1
                                      whenLeft r2 print
                                      whenRight r2 (d2 -> 
                                                       [rest of code...] )
                             )
    

    当然,这会导致代码混乱,所以每当我这样做时,我都会确保在遇到Left 时结束计算。 Except monad 非常适合这个。使用它,代码将被重写如下:

    get :: String -> ExceptT String IO ()
    get id = do
               r1 <- liftIO $ getData1 id
               whenLeft r1 throwError 
               r2 <- liftIO $ getData2 $ d2Id (fromRight r1)
               whenLeft r2 throwError
               r3 <- liftIO $ getData3 $ d3Id (fromRight r2)
               whenLeft r3 throwError
               liftIO $ process (fromRight r1) (fromRight r2) (fromRight r3)
    

    这将使fromRight 语句永远不会失败,因为只要 r1、r2 或 r3 为 Left,计算就会抛出错误并停止。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-04
      • 1970-01-01
      • 1970-01-01
      • 2018-06-05
      相关资源
      最近更新 更多