Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can Haskell exceptions only be caught inside the IO monad?

Can anybody explain why exceptions may be thrown outside the IO monad, but may only be caught inside it?

like image 210
fuz Avatar asked Sep 04 '10 15:09

fuz


People also ask

Why is IO a Monad in Haskell?

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.

Is IO a Monad Haskell?

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.

Does Haskell have exceptions?

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.

Is IO pure Haskell?

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 .


1 Answers

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.)

like image 92
Roman Cheplyaka Avatar answered Sep 18 '22 12:09

Roman Cheplyaka