让我们说我想重做以下功能:
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,然后处理错误。
这是一种迂回的方式,特别是因为有catch
和handle
这样的函数允许我使用处理程序来处理错误。但是他们输入了IO
:
handle :: Exception e => (e -> IO a) -> IO a -> IO a
catch :: Exception e => IO a -> (e -> IO a) -> IO a
如何干净地重构上面的代码,以便我可以处理IO错误并将其传递给ErrorT
monad?
答案 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。这仍然不完美,因为你需要显式地包装所有异常抛出的代码片段。但这并不可怕。