Can anybody explain why exceptions may be thrown outside the IO monad, but may only be caught inside it?
The I/O monad contains primitives which build composite actions, a process similar to joining statements in sequential order using `;' in other languages. Thus the monad serves as the glue which binds together the actions in a program.
The IO type constructor provides a way to represent actions as Haskell values, so that we can manipulate them with pure functions. In the Prologue chapter, we anticipated some of the key features of this solution. Now that we also know that IO is a monad, we can wrap up the discussion we started there.
There are three different ways exceptions can be thrown in Haskell: Synchronously thrown: an exception is generated from IO code and thrown inside a single thread. Asynchronously thrown: an exception is thrown from one thread to another thread to cause it to terminate early.
The point is - Haskell is always pure, IO doesn't change this. So, our impure, non-independent codes have to get a common dependency - we have to pass a RealWorld .
One of the reasons is the denotational semantics of Haskell.
One of the neat properties of (pure) Haskell functions is their monotonicity -- more defined argument yields more defined value. This property is very important e.g. to reason about recursive functions (read the article to understand why).
Denotation of exception by definition is the bottom, _|_
, the least element in poset corresponding to the given type. Thus, to satisfy monotonicity requirement, the following inequality needs to hold for any denotation f
of Haskell function:
f(_|_) <= f(X)
Now, if we could catch exceptions, we could break this inequality by "recognizing" the bottom (catching the exception) and returning more defined value:
f x = case catch (seq x True) (\exception -> False) of True -> -- there was no exception undefined False -> -- there was an exception, return defined value 42
Here's complete working demonstration (requires base-4 Control.Exception):
import Prelude hiding (catch) import System.IO.Unsafe (unsafePerformIO) import qualified Control.Exception as E catch :: a -> (E.SomeException -> a) -> a catch x h = unsafePerformIO $ E.catch (return $! x) (return . h) f x = case catch (seq x True) (\exception -> False) of True -> -- there was no exception undefined False -> -- there was an exception, return defined value 42
Another reason, as TomMD noted, is breaking referential transparency. You could replace equal things with equal and get another answer. (Equal in denotational sense, i.e. they denote the same value, not in ==
sense.)
How would we do this? Consider the following expression:
let x = x in x
This is a non-terminating recursion, so it never returns us any information and thus is denoted also by _|_
. If we were able to catch exceptions, we could write function f such as
f undefined = 0 f (let x = x in x) = _|_
(The latter is always true for strict functions, because Haskell provides no means to detect non-terminating computation -- and cannot in principle, because of the Halting problem.)
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