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

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

Leave a Comment

techhipbettruvabetnorabahisbahis forumu