如何简化(IO(a b之一))中的错误处理

时间:2018-11-22 08:14:55

标签: haskell

我以以下场景为例,学习如何以简单的方式处理错误。该方案基本上是从环境变量读取文件路径,然后读取并打印带有文件路径的文件。

以下代码可以工作,但我不喜欢printFile,因为它嵌套了case of,有点难以阅读。我想知道是否有一种干净的方法可以摆脱它并使printFile函数保持平坦而不使用lookupEnv吗?

您如何简化此错误处理流程?

module Main where

import Control.Exception (IOException, handle, throw)
import System.Environment (getEnv)
import System.IO.Error (isDoesNotExistError)

data MissingError
  = MissingEnv String
  | MissingFile String
  deriving (Show)

main :: IO ()
main = do
  eitherFile <- printFile
  either print print eitherFile

getEnv' :: String -> MissingError -> IO (Either MissingError String)
getEnv' env err = handle (missingEnv err) $ Right <$> (getEnv env)

readFile' :: FilePath -> MissingError -> IO (Either MissingError String)
readFile' path err = handle (missingFile err) $ Right <$> (readFile path)

missingEnv :: MissingError -> IOException -> IO (Either MissingError String)
missingEnv err = const $ return $ Left err

missingFile :: MissingError -> IOException -> IO (Either MissingError String)
missingFile err e
  | isDoesNotExistError e = return $ Left err
  | otherwise = throw e

printFile :: IO (Either MissingError String)
printFile = do
  eitherFilePath <- getEnv' "FOLDER" (MissingEnv "FOLDER")
  case eitherFilePath of
    Left err -> return $ Left err
    Right path -> readFile' path (MissingFile path)

2 个答案:

答案 0 :(得分:5)

您可以使用ExceptT monad变压器。我没有尝试运行以下建议的更改,但是可以编译,所以我希望它能起作用。

首先,导入包含ExceptT的模块:

import Control.Monad.Trans.Except

接下来,更改printFile函数:

printFile :: IO (Either MissingError String)
printFile = runExceptT $ do
  path <- ExceptT $ getEnv' "FOLDER" (MissingEnv "FOLDER")
  ExceptT $ readFile' path (MissingFile path)

您有返回IO (Either MissingError String)的函数,因此将它们包装在ExceptT中会得到do表示法,使您可以访问有效嵌入{{1}的内容中嵌入的String }。

然后用ExcepT MissingError IO String解开ExceptT返回值。

答案 1 :(得分:3)

使用ExceptT的建议当然是一个好建议,但是恕我直言,建议的答案仍然有些冗长,您可以通过在整个代码中简单地“停留”在ExceptT monad中来进一步。另外,我也不建议在所有地方都处理IO异常。即使使用很小的代码库,您也会很快失去对代码的监督。 tryIOError在这方面很有用。最后,重新考虑错误的定义也会产生更容易理解和更可靠的解决方案。最终结果如下所示:

module Main where

import Data.Bifunctor       (first)
import Control.Monad.Except (ExceptT(..), runExceptT)
import System.Environment   (getEnv)
import System.IO.Error      (tryIOError, isDoesNotExistError)

data MyError = MissingError String
             | SomeIOError IOError
               deriving (Show)

main :: IO ()
main = do
  result <- runExceptT printFile
  print result

getEnv' :: String -> ExceptT MyError IO String
getEnv' env = mapIOError ("getting env var " ++ env) $ getEnv env

readFile' :: FilePath -> ExceptT MyError IO String
readFile' path = mapIOError ("reading file " ++ path) $ readFile path

printFile :: ExceptT MyError IO String
printFile = do
  path <- getEnv' "FOLDER"
  readFile' path

mapIOError :: String -> IO a -> ExceptT MyError IO a
mapIOError msg = ExceptT . fmap (first mapError) . tryIOError
    where mapError err | isDoesNotExistError err = MissingError msg
          mapError err                           = SomeIOError err