I trying to read some value out of a file and catch every exception that might occur (in the mindset of "Easier to ask for forgiveness than permission"). I am having trouble catching the Prelude.read: no parse
exception though. To tell try
that it should catch every exception I define tryAny
with the explicit type SomeException
which to my knowledge is the "super type" of every exception:
import Control.Exception (try,SomeException)
tryAny :: IO a -> IO (Either SomeException a)
tryAny = try
With tryAny
I seem to be able to catch IO errors:
> tryAny (fromFile "nonExistingFileName")
Left nonExistingFileName: openFile: does not exist (No such file or directory)
But the read error will not be caught:
> tryAny (return ((read "a")::Int))
Right *** Exception: Prelude.read: no parse
What can I do to catch every exception?
return
does not evaluate its argument, and thus doesn't throw any exceptions. The evaluation happens outside of tryAny
, when you try to print the result.
Use evaluate
(possibly together with force
from Control.DeepSeq
, depending on your real situation) for that.
After a lot of experimenting with evaluate
, force
and try
, following the suggestions of @Roman Cheplyaka, I've come up with this solution, which seems almost blazingly obvious now I see the final result:
import Control.DeepSeq (force,NFData)
import Control.Exception (try,SomeException)
tryAnyStrict :: NFData a => IO a -> IO (Either SomeException a)
tryAnyStrict = try.evaluateIoCompletely
evaluateIoCompletely :: NFData a => IO a -> IO a
evaluateIoCompletely ioA = do
a <- ioA
evaluate $ force a
This solution splits the process up into a function that will enforce strict evaluation and throw all exceptions and a wrapper that will catch these exceptions. force
returns a pure haskell object that itself has to be evaluated first to force deep evaluation of a
. From the documentation:
force x = x `deepseq` x
force x fully evaluates x, and then returns it. Note that force x only performs evaluation when the value of force x itself is demanded, so essentially it turns shallow evaluation into deep evaluation.
There are a few subtle points of this implementation that went wrong in my earlier tries:
ioA
directly as input to force
instead of a
then you will end up with an object of type IO (IO a)
, as evaluate
has type b -> IO b
. This immediate problem can be solved with some unwrap function of type IO(IO a) -> IO a
, but then you will get a problem with the typeclass assumption of NFData a
, which now needs to be NFData (IO a)
. But for IO a
there seems to be no instance declarations available, at least for a::(String,String)
.tryAnyStrict = do a <- ioA; (try.evaluate.force) a
. The problem then is that exceptions arising from a <- ioA
will not be caught, for example if ioA
is the result of serialized <- readFile; return $ read serialized
and the file that is to be read from does not exist.try.evaluateIoCompletely
in your code without type information, because try needs to know which exception it is supposed to catch. This information is now supplied via the return type of tryAnyStrict
which resolves the type e
of try :: Exception e => IO a -> IO (Either e a)
to the most general exception type SomeException
.Now after I got tryAnyStrict
working I still have problems with lazy evaluation, because if a <- ioA
fails - where ioA is the result of read
on a string read lazily from a file - (for example the file content does not have the format necessary for read
) then the file is still not completely read and thus still opened. A subsequent write on the file will fail ("openFile: resource busy"). So... I might actually rewrite the whole code yet again using readMay
or readMaybe
as suggested by @kqr and @Roman Cheplyaka and also force a strict read of the file directly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With