How to handle multiple errors in Haskell IO context

Viewed 39

I'm learning Haskell (from LYAH) and just starting to understand monads. I am trying to write a command line program which parses the command line, opens and reads a file (or files) whose paths are specified as parameters, does some processing, and outputs the result. Of itself this is not too difficult, but I am trying to figure out how to do the error handling in a clear, idiomatic manner. Using Either as a monad in a do block seems a good way of handling errors in pure functional code, but the top level of my program has to do IO, which can also fail with errors, and the top level main function is of type IO () not of type Either a b, add I can't figure out how to combine them.

To make my issue clearer, here are some examples of ways I've tried to handle it, but I'm not happy with any of them.

Please note: Earlier this question was marked as a duplicate of one about handling many levels of indentation but that is not what I am asking - I'm asking about how to handle errors in an IO context. I've re-ordered the examples to make that clearer.

  1. die on error:
main = do
 args <- getArgs
 filename <- case parseArgs args of
   Left err -> die $ "ERROR: " ++ err
   Right f -> return f

 loadResult <- loadFile filename
 contents <- case loadResult of
   Left err -> die $ "ERROR: " ++ err
   Right c -> return c

 res <- case process contents of
   Left err -> die $ "ERROR: " ++ err
   Right r -> return r

 printReport filename res

This works but it is inelegant and inflexible. There's no chance of doing cleanup or moving on to another file after failure.

  1. The either chain:
main = do
  args <- getArgs
  case parseArgs args of
    Left err -> putStrLn $ "ERROR: " ++ err
    Right filename -> do
      loadResult <- loadFile filename
      case loadResult of
        Left err -> putStrLn $ "ERROR: " ++ err
        Right contents ->
          case process contents of
            Left err -> putStrLn $ "ERROR: " ++ err
            Right res -> printReport filename res

This is nasty because it keeps stepping to the right, and is not practical for longer sequences.

  1. I understand that exceptions might be another possibility, but LYAH discourages their use and suggests using IO (Either a b) instead. However, I can't see how to do that.

What would be the idiomatic way to deal with this?

0 Answers
Related