Can anybody explain why exceptions may be thrown outside the IO monad, but may only be caught inside it?
3 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.)

- 37,738
- 7
- 72
- 121
-
1Okay. This seems to be one of the difficult mathematical backrounds behind haskell. Thank you for your brief description. – fuz Sep 05 '10 at 02:12
-
1Sorry but this answer is completely baffling for what I think is a very simple question... but this probably reflects more on my lack of understanding of Haskell rather than your answer. – dodgy_coder May 31 '12 at 10:19
-
1dodgy_coder: I'm sorry it wasn't useful for you. There may be different perspectives from which one may answer this question. You could say that you can(not) do it because types of corresponding functions do (not) allow it. Then of course you might ask why the types are such as they are. The answer is "there are good reasons to (not) allow such things", and I tried to outline these reasons above. – Roman Cheplyaka May 31 '12 at 10:36
-
Thanks, you gave a place to view mathematical behind Haskell – TorosFanny Jan 24 '13 at 06:35
-
But I remember "This is unsafe, since Haskell's error is just sugar for undefined" cited from "http://www.haskell.org/haskellwiki/Error_vs._Exception". Could you check if you make a mistake by saying "Denotation of exception by definition is the bottom" since error and exception are different. – TorosFanny Jan 26 '13 at 14:26
-
1@TorosFanny: good point. In the semantics that I consider here the two notions are indeed confounded (which is customary); but you can certainly imagine one where they are not; and in such semantics my argument would be invalid. If I were writing this answer today, it would be very different :) The main argument would be that catching exceptions doesn't play well with laziness, because it depends on the evaluation order. – Roman Cheplyaka Jan 26 '13 at 18:59
Because exceptions can break referential transparency.
You're probably talking about exceptions that are actually direct results of the input. For example:
head [] = error "oh no!" -- this type of exception
head (x:xs) = x
If you are lamenting not being able to catch errors like this then I assert to you that the functions shouldn't be relying on error
or any other exceptions but should instead use a proper return type (Maybe
, Either
, or perhaps MonadError). This forces you to deal with the exceptional condition in a more explicit manner.
Unlike the above (and what causes the issue behind your question), exceptions can be from signals such as out of memory conditions that are completely independent of the value being computed. This is clearly not a pure concept and must live in IO.

- 64,245
- 7
- 109
- 166
-
2Can you give an example of breaking referential transparency in this way? – Roman Cheplyaka Sep 04 '10 at 20:15
-
So you want to tell me, that excpetions are not the way to think functionally, so I should generally try to avoid them? Great! – fuz Sep 05 '10 at 01:44
-
Roman: What I ment was that if you could catch exceptions in pure code and base your result on those exceptions, such an action would break referential transparency. – Thomas M. DuBuisson Sep 05 '10 at 03:51
-
Ah I see. It's maybe also a problem, that your code can just escape from the founction where it's defined, which may also break referential transparency. – fuz Sep 05 '10 at 07:03
-
TomMD: I see perfectly what you meant, I just wanted to see a concrete example of breaking RT. Anyway, I came up with my own and expanded my answer... – Roman Cheplyaka Sep 05 '10 at 08:12
I might be wrong in my explanation, but that's how I understand it.
Since functions are pure in Haskell, the compiler has the right to evaluate them in any order he wishes and it still produces the same result. For example, given function:
square :: Int -> Int
square x = x * x
expression square (square 2)
can be evaluated in different ways, but it always reduces to the same result which is 16.
If we call square
from somewhere else:
test x = if x == 2
then square x
else 0
square x
can be evaluated later, "outside" the test
function when the value is actually needed. At that moment the call stack can be completely different from the one you would expect say in Java.
So even if we wanted to catch a potential exception thrown by square
, where should you place the catch
part?

- 11,946
- 11
- 59
- 85