Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exception handling for 'readFile'

I'm trying to add a simple handler to the readFile function:

readHandler :: IOError -> IO ()  
readHandler e  
    | isDoesNotExistError e = putStrLn "The file does not exist"
    | otherwise = putStrLn "Something went wrong"  

main = do
    // stop executing if there is no file   
    contents <- (readFile "path.xml") `catch` readHandler
    // generates [(x,y),(x,y)]
    coordinates = parseXML contents
    // I want to do something with the coordinates
    nextFunction coordinates

When I try to compile this I get the error:

Couldn't match type `()' with `[Char]'
Expected type: IOError -> IO String
  Actual type: IOError -> IO ()
In the second argument of `catch', namely `readHandler'
In a stmt of a 'do' block:
  contents <- (readFile "path") `catch` readHandler
In the expression:
  do { contents <- (readFile "path") `catch` readHandler;
       putStrLn contents }

Thus readHandler :: IOError -> IO () should be readHandler :: IOError -> IO String.

But this way I can't print the error message.

How should I solve this problem?

like image 686
G-J Avatar asked Jan 18 '14 19:01

G-J


2 Answers

What should the type of catch (readFile "path") readHandler be?

Obviously if the file exists, we'd want it to be a String, catch shouldn't change it's type, so somehow we have to produce a String no matter what. Since if an exception is thrown, readHandler is run, it must also produce a string.

In this way catch is acting like a very sophisticated if expression :) However, it's less than ideal since we don't want to keep running our function with some random String that didn't come from the file.

Instead we could do something like

 main = handle readHandler $ do
    ...

Since now we just have to produce an IO (), trivial.

If this doesn't float your boat for whatever reason, another sane choice would be to transform that irritating exception to a more pleasant Either

 main = do
   strOrExc <- try $ readFile "foo"
   case strOrExc of
     Left except -> print except
     Right contents -> putStrLn contents

and of course you could handle this Exception e => Either e a in whatever way brings you happiness.

There is of course a final alternative, bring the whole program to a screeching halt right then and there. We can do this just by changing readHandler

import System.Exit

readHandler :: IOError -> IO a
readHandler = putStrLn "Buckle your seatbelts" >> exitFailure
like image 98
Daniel Gratzer Avatar answered Oct 09 '22 23:10

Daniel Gratzer


catch has type

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

So if the first parameter of catch is IO String, then the second argument (which is a function), has to return that either. You could change your readHandler like so:

readHandler :: IOError -> IO String
readHandler e  
    | isDoesNotExistError e = do putStrLn "The file does not exist" ; return ""
    | otherwise = do putStrLn "Something went wrong" ; return ""

But I am not sure, whether that's a wanted "side effect" (returning an empty string).

like image 32
kaan Avatar answered Oct 09 '22 22:10

kaan