The documentation says:
The throwIO variant should be used in preference to throw to raise an exception within the IO monad because it guarantees ordering with respect to other IO operations, whereas throw does not.
I'm still confused after reading it. Is there an example to show that throw will cause a problem whereas throwIO will not?
Additional question:
Is the following statement correct?
throw
is being used to throw an exception in an IO, then the order of the exception is not guaranteed.throw
is being used to throw an exception in a non-IO value, then the order of the exception is guaranteed.If I need to throw an exception in a Monad Transformer, which I have to use throw
instead of throwIO
, does it guarantee the order of the exception?
I think the docs could be improved. The issue you need to keep in mind with throw
and the like is that throw
returns a bottom value that "explodes" (raises an exception) when evaluated; but whether and when evaluation happens is difficult to control due to laziness.
For example:
Prelude Control.Exception> let f n = if odd n then throw Underflow else True
Prelude Control.Exception> snd (f 1, putStrLn "this is fine")
this is fine
This could arguably be what you want to happen, but usually not. e.g. rather than the tuple above you might end up with a big data structure with a single exploding element that causes an exception to be raised after your web server returns 200 to the user, or something.
throwIO
allows you to sequence raising an exception just as if it was another IO action, so it can be tightly controlled:
Prelude Control.Exception> throwIO Underflow >> putStrLn "this is fine"
*** Exception: arithmetic underflow
...just like doing print 1 >> print 2
.
But note that you can actually replace throwIO
with throw
, for instance:
Prelude Control.Exception> throw Underflow >> putStrLn "this is fine"
*** Exception: arithmetic underflow
Since now the exploding value is of type IO a
. It's actually not clear to me why throwIO
exists other than to document an idiom. Maybe someone else can answer that.
As a final example, this has the same issue as my first example:
Prelude Control.Exception> return (throw Underflow) >> putStrLn "this is fine"
this is fine
throw
is a generalization of undefined
, while throwIO
is an actual IO
action. A key difference is that many laws don't quite hold when strictness is considered (i.e., when you have undefined
(or throw
) and seq
).
> (throw Underflow :: IO ()) `seq` ()
*** Exception: arithmetic underflow
> (throw Underflow >>= pure) `seq` ()
()
Hence contradicting the law m >>= pure = m
. throwIO
doesn't have that issue, so it is the more principled way of throwing exceptions.
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