I have a program that is supossed to parse the output lines of a program, and produce some output data structure that contains some information extracted from those lines. For doing this I'm using turtle
:
import Turtle
import qualified Control.Foldl as F
import Control.Monad.Except
-- Just for illustration purposes
newtype Result a = Result [a]
type Error = String
collectOutput :: (MonadIO m, MonadError Error m) => Text -> m Result
collectOutput cmd = Result <$> fold (runCmd cmd) F.list
-- Yes, I know turtle does also streaming, but I need to return all the results at the same time. Sorry...
runCmd ::Text -> Shell Result
runCmd cmd = do
line <- inproc cmd [] empty
tryParseOutput
Now the problem comes when trying to define tryParseOutput
. I would like to abort the command (spawned with inproc cmd [] empty
) as soon as an error occurs. For doing this, the the only way I see is by using the die
function, which will throw an IOError
:
tryParseOutput line =
case parseOutput line of
Left parseErr -> die $ show parseErr
Right res -> return res
But now this means that I need to modify collectOutput
to handle IOExceptions
:
collectOutput :: (MonadIO m, MonadError Error m) => Text -> m Result
collectOutput cmd = do
mRes <- liftIO $ collectOutputIO cmd
case mRes of
Left err -> throwError err
Right res -> return res
collectOutputIO :: Text -> IO (Either Error Result)
collectOutputIO cmd =
(Right . Result <$> fold (runCmd cmd) F.list) `catch` handler
where
handler :: IOException -> Either Error Result
handler ex = show ex
While this works, I don't like the fact that I'm throwing an IO exception for a parsing error, and there is some exception catching and case analysis involved. So I was wondering whether this could be solved in a more elegant manner (maybe more turtle
idiomatic), or I should forget about using MonadError
and accept IOError
as a fact of life of turtle
shell scripts?