Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the difference between throw and throwIO

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?

  1. If throw is being used to throw an exception in an IO, then the order of the exception is not guaranteed.
  2. If 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?

like image 745
Leo Zhang Avatar asked Jan 01 '19 23:01

Leo Zhang


2 Answers

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
like image 194
jberryman Avatar answered Nov 15 '22 08:11

jberryman


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.

like image 45
Li-yao Xia Avatar answered Nov 15 '22 07:11

Li-yao Xia