我以以下场景为例,学习如何以简单的方式处理错误。该方案基本上是从环境变量读取文件路径,然后读取并打印带有文件路径的文件。
以下代码可以工作,但我不喜欢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)
答案 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