如何在Haskell中使用ErrorT内的catch?

时间:2013-11-30 01:03:47

标签: haskell error-handling

让我们说我想重做以下功能:

runCmd :: FilePath -> ErrorT String IO ()
runCmd cmd = do
  Left e <- liftIO $ tryIOError $ do
    (inp, outp, errp, pid) <- runInteractiveProcess cmd [] Nothing Nothing
    mapM_ (`hSetBuffering` LineBuffering) [inp, outp, errp]
    forever (hGetLine outp >>= putStrLn) -- IO error when process ends
  unless (isEOFError e) $ throwError $ "IO Error: " ++ ioeGetErrorString e

它尝试运行cmd并读取输出。如果它因IO错误而失败,我会用tryIOError捕获它,将其传递给封闭的ErrorT monad,然后处理错误。

这是一种迂回的方式,特别是因为有catchhandle这样的函数允许我使用处理程序来处理错误。但是他们输入了IO

handle :: Exception e => (e -> IO a) -> IO a -> IO a
catch  :: Exception e => IO a -> (e -> IO a) -> IO a

如何干净地重构上面的代码,以便我可以处理IO错误并将其传递给ErrorT monad?

2 个答案:

答案 0 :(得分:1)

首先,我会避免使用ErrorT。让runCmd只返回IO ()(如果出现问题,则抛出IOError),并将错误处理推迟给调用者:

runCmd :: FilePath -> IO ()
runCmd cmd =
  handleIOError (\e -> if isEOFError e then return () else ioError e) $ do
    -- same code as before
    (inp, outp, errp, pid) <- runInteractiveProcess cmd [] Nothing Nothing
    mapM_ (`hSetBuffering` LineBuffering) [inp, outp, errp]
    forever (hGetLine outp >>= putStrLn)
  where handleIOError = flip catchIOError

caller :: IO ()
caller = catchIOError (runCmd "/bin/ls") $ \e ->
  -- error handling code

如果您需要在其他地方的IOError代码中捕获ErrorT,可以在那里使用liftIO

errorTCaller :: ErrorT String IO ()
errorTCaller = liftIO caller

答案 1 :(得分:1)

如果你真的想使用ErrorT,你可以尝试这样的事情

import Control.Exception
import Control.Monad.Error

wrapException :: IO a -> ErrorT String IO a
wrapException io = do
  either <- liftIO $ tryJust Just io
  case either of
    Left e  -> throwError . show $ (e :: SomeException)
    Right v -> return v

但这并不完美,因为在可以抛出异常的情况下,你仍然只能被IO限制。您可以做的就是使用

catchException :: ErrorT String IO a -> ErrorT String IO a
catchException = either throwError return <=< wrapException . runErrorT

这样做是抓住任何传播的异常并将它们捕获回ErrorT monad。这仍然不完美,因为你需要显式地包装所有异常抛出的代码片段。但这并不可怕。