Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I correctly use Control.Exception.catch in Haskell?

Tags:

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.

like image 821
Eyal Avatar asked Aug 05 '13 07:08

Eyal


People also ask

Does Haskell have exception handling?

The Haskell runtime system allows any IO action to throw runtime exceptions. Many common library functions throw runtime exceptions. There is no indication at the type level if something throws an exception. You should assume that, unless explicitly documented otherwise, all actions may throw an exception.

How do you use try in Haskell?

try :: Exception e => IO a -> IO (Either e a) try takes an IO action to run, and returns an Either . If the computation succeeded, the result is given wrapped in a Right constructor. (Think right as opposed to wrong). If the action threw an exception of the specified type, it is returned in a Left constructor.


2 Answers

Let's examine what happens in the first case:

catch (return $ head []) $ \(e :: SomeException) -> return "good message" 

You create thunk head [] which is returned 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.

like image 84
Petr Avatar answered Nov 09 '22 02:11

Petr


return $ head [] 

wraps head [] in an IO action (because catch has an IO type, otherwise it would be any monad) and returns it. There is nothing caught because there is no error. head [] itself is not evaluated at that point, thanks to lazyness, but only returned. So, return only adds a layer of wrapping, and the result of your whole catch expression is head [], quite valid, unevaluated. Only when GHCi or your program actually try to use that value at some later point, it will be evaluated and the empty list error is thrown - at a different point, however.

print $ head [] 

on the other hand immediately evaluates head [], yielding an error which is subsequently caught.

You can also see the difference in GHCi:

Prelude> :t head [] head [] :: a  Prelude> :t return $ head [] return $ head [] :: Monad m => m a  Prelude> :t print $ head [] print $ head [] :: IO ()  Prelude> return $ head [] -- no error here!  Prelude> print $ head [] *** Exception: Prelude.head: empty list    

To avoid that, you can perhaps just force the value:

Prelude> let x = head [] in x `seq` return x *** Exception: Prelude.head: empty list 
like image 42
firefrorefiddle Avatar answered Nov 09 '22 03:11

firefrorefiddle