How do I correctly use Control.Exception.catch in Haskell?
exception, haskell
Solution
Let's examine what happens in the first case:
catch (return $ head []) $ \(e :: SomeException) -> return "good message"
You create thunk `head []` which is `return`ed as an `IO` action. This thunk doesn't throw any exception, because it isn't evaluated, so the whole call `catch (return $ head []) $ ...` (which is of type `IO String`) produces the `String` thunk without an exception. The exception occurs only when ghci tries to print the result afterwards. If you tried
catch (return $ head []) $ \(e :: SomeException) -> return "good message"
>> return ()
instead, no exception would have been printed.
This is also the reason why you get _"* Exception: Prelude.head: empty list_. GHCi starts to print the string, which starts with `"`. Then it tries to evaluate the string, which results in an exception, and this is printed out.
Try replacing `return` with `evaluate` (which forces its argument to WHNF) as
catch (evaluate $ head []) $ \(e :: SomeException) -> return "good message"
then you'll force the thunk to evaluate inside `catch` which will throw the exception and let the handler intercept it.
In the other case
catch (print $ head []) $ \(e :: SomeException) -> print "good message"
the exception occurs inside the `catch` part when `print` tries to examine `head []` and so it is caught by the handler.
Update: As you suggest, a good thing is to force the value, preferably to its full normal form. This way, you ensure that there are no "surprises" waiting for you in lazy thunks. This is a good thing anyway, for example you can get hard-to-find problems if your thread returns an unevaluated thunk and it is actually evaluated in another, unsuspecting thread.
Module `Control.Exception` already has `evaluate`, which forces a thunk into its WHNF. We can easily augment it to force it to its full NF:
import Control.DeepSeq
import Control.Seq
import Control.Exception
import Control.Monad
toNF :: (NFData a) => a -> IO a
toNF = evaluate . withStrategy rdeepseq
Using this, we can create a strict variant of `catch` that forces a given action to its NF:
strictCatch :: (NFData a, Exception e) => IO a -> (e -> IO a) -> IO a
strictCatch = catch . (toNF =<<)
This way, we are sure that the returned value is fully evaluated, so we won't get any exceptions when examining it. You can verify that if you use `strictCatch` instead of `catch` in your first example, it works as expected.
Problem
Can someone please explain the difference between the behavior in ghci of the following to lines: ``` catch (return $ head []) $ \(e :: SomeException) -> return "good message" ``` returns ``` "*** Exception: Prelude.head: empty list ``` but ``` catch (print $ head []) $ \(e :: SomeException) -> print "good message" ``` returns ``` "good message" ``` Why isn't the first case catching the exception? Why are they different? And why does the first case put a double quote before the exception message? Thanks.