2

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?

Damian Nadales
  • 4,907
  • 1
  • 21
  • 34
  • "throwing an IO exception for a parsing error" perhaps you could define your own `ParsingException`. It would let you include more detailed errors. – danidiaz Dec 21 '17 at 19:39
  • Problem is that if I use `die` I cannot help throwing an IO Exception. Unless I just use `throw` inside a `Shell` monad and the I could use the exception you mentioned. I guess that is a bit better thanks for the tip – Damian Nadales Dec 21 '17 at 22:06
  • 1
    For what it's worth, I once wrote a wrapper for the "process" package that let you exit early (and kill the external process) by returning a `Left` instead of throwing exceptions: http://hackage.haskell.org/package/process-streaming-0.9.1.2/docs/System-Process-Streaming.html see the `executeFallibly` function. – danidiaz Dec 22 '17 at 14:19

0 Answers0