Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't catch "Prelude.read: no parse" exception with Control.Exception.try

Tags:

haskell

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?

like image 719
Perseids Avatar asked Nov 04 '13 13:11

Perseids


2 Answers

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.

like image 129
Roman Cheplyaka Avatar answered Sep 18 '22 18:09

Roman Cheplyaka


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:

  • If you use 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).
  • Instead of using two functions you could just use 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.
  • You cannot inline 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.

like image 20
Perseids Avatar answered Sep 20 '22 18:09

Perseids