I am attempting to write a simple function to safely read a file (if it exists) and do nothing if the file does not exist:
safeRead :: String -> IO ()
safeRead path = readFile path `catch` handleExists
where handleExists e
| isDoesNotExistError e = return ()
| otherwise = throwIO e
This fails at compile time with
Couldn't match type ‘[Char]’ with ‘()’
Expected type: IO ()
Actual type: IO String
In the first argument of ‘catch’, namely ‘readFile path’
In the expression: readFile path `catch` handleExists
This makes sense since :t readFile
is readFile :: FilePath -> IO String
. e.g a function that returns IO String
(and IO String
is not the same as IO ()
)
Changing the signature to String -> IO String
Couldn't match type ‘()’ with ‘[Char]’
Expected type: IOError -> IO String
Actual type: IOError -> IO ()
In the second argument of ‘catch’, namely ‘handleExists’
In the expression: readFile path `catch` handleExists
Which also makes sense since handleExists has type IO ()
To save everyone lookup, catch is imported with:
import Control.Exception
the signature of catch is:
catch :: Exception e => IO a -> (e -> IO a) -> IO a
My real question is, how can I write this kind of error safe, flexible code in Haskell? More specifically what would be the change I would have to make to this function to have it handle both a success case and a failure case?