If I understood rightly, exceptions in Haskell basically intended to deal within IO monad. At least exception could be caught inside IO monad.
But sometimes even pure functions may throw an exception, e.g. read "..." :: Int
(when reading string does not represent integer), operator (!!)
(when we trying to get item out of range of the list), and so forth. And this is true behavior, I don't deny. However, I would not want to change signature of function just to get it chance to catch possible exception, because in this case I have to change signature all the functions by call stack before.
Is there some pattern to deal with exceptions more comfortable in Haskell, out of IO monad? May be I should use unsafePerformIO
in this case? How "safe" to use unsafePerformIO
just for catching exceptions in pure functions?
In pure code, it's usually best to avoid exceptions from happening in the first place. That is, don't use head
unless you're absolutely positive that the list isn't empty, and use reads
and pattern matching instead of read
to check for parse errors.
I think a good rule of thumb is that exceptions in pure code should only come from programming errors, i.e. calls to error
, and these should be treated as bugs, not something an exception handler can deal with.
Note that I'm only talking about pure code here, exceptions in IO
have their uses when dealing with the exceptional cases that sometimes happen when interfacing with the "real world". However, pure mechanisms like Maybe
and ErrorT
are easier to work with, so they are often preferred.
That's what monads are for! (Well, not just that, but exception handling is one use of monadic idioms)
You do change the signature of functions that can fail (because they change semantics, and you want to reflect as much of the semantics as possible in the type, as a rule of thumb). But code that uses these functions does not have to pattern match on every failable function; they can bind if they don't care:
head :: [a] -> Maybe a
eqHead :: (Eq a) => [a] -> Maybe [a]
eqHead xs = do
h <- head xs
return $ filter (== h) xs
So eqHead
cannot be written "purely" (a syntactic choice whose alternatives I would like to see explored), but it also doesn't really have to know about the Maybe
-ness of head
, it only has to know that head
can fail in some way.
It's not perfect, but the idea is that functions in Haskell do not have the same style as Java. In typical Haskell design, an exception does not usually occur deep in the call chain. Instead, the deep call chain is all pure, when we know that all arguments are fully defined and well-behaved, and validation occurs at the outermost layers. So an exception bubbling up from the deep is not really something that needs to be supported in practice.
Whether the difficulty of doing so causes the design patterns, or the design patterns cause the lack of features supporting this is a matter of debate.
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