问题
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.
回答1:
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.
回答2:
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
回答3:
GHCi works in the IO monad and return
for IO
doesn't force its argument. So return $ head []
doesn't throw any exception, and an exception that isn't thrown can't be caught. Printing the result afterwards throws an exception, but this exception isn't in the scope of catch
anymore.
回答4:
The type IO a
has two parts:
- The structure, the part that goes out and performs side-effects. This is represented by
IO
. - The result, the pure value held inside. This is represented by
a
.
The catch
function only catches exceptions when they break through the IO
structure.
In the first example, you call print
on the value. Since printing a value requires performing I/O with it, any exceptions raised within the value end up in the structure itself. So catch
intercepts the exception and all is well.
On the other hand, return
does not inspect its argument. In fact, you are guaranteed by the monad laws that calling return
does not affect the structure at all. So your value simply passes right through.
So if your code isn't affected by the exception, then where is the error message coming from? The answer is, surprisingly, outside your code. GHCi implicitly tries to print
every expression that is passed to it. But by then, we are already outside the scope of the catch
, hence the error message you see.
来源:https://stackoverflow.com/questions/18052619/how-do-i-correctly-use-control-exception-catch-in-haskell