exceptions vs. Either

Exceptions are convenient in that you can rely on libraries throwing them instead of prechecking for valid values yourself (for instance, why check that the argument to Char.digitToInt is valid if digitToInt does so already), and you don't have to modify a lot of function signatures. Unfortunately, in the face of lazy evaluation, they can get thrown in unexpected places. Even with Exception.evaluate, or code which is already explicitly sequenced in the IO monad, like "t <- Exception.evaluate (map Char.digitToInt ['a'..'g'])" an exception will only be thrown when you inspect the last element of 't'. (digitToInt confusingly accepts hex "digits"---shouldn't it be "higitToInt" then?). So it seems to me that if you are, say, checking input, the options are to handle exceptions from the checking code but expect them to come from the processing code, or decorate all checking code with Rights and Lefts. Problems with the first option are that checking code could also trigger some exceptions, and Prelude functions throw undescriptive errors like "user error" or low level ones like "refuted pattern match" and catching them over the entire program means you could stifle a lot of real errors. This implies that you have to make specific exceptions and convert Prelude and library exceptions into yours as low down as possible, which is cluttering but maybe not as cluttering as Either. Problems with the second option are many of the problems that lead to us wanting exceptions in the first place. Using Either seems much simpler and functional-friendly. So then, in what contexts are exceptions appropriate in haskell?

On Monday 02 August 2004 21:09, Evan LaForge wrote:
Exceptions are convenient [...] [but] an exception will only be thrown when you evaluate t.
So it seems to me that if you are, say, checking input, the options are to handle exceptions from the checking code but expect them to come from the processing code, or [not use eceptions] [...] So then, in what contexts are exceptions appropriate in haskell?
I think you're right that exceptions aren't useful for input checking. You really need something that will check all the input at once. i.e., evaluating any part of the checked input causes all input checks to be performed. The easiest way to do this is with a monad or some such - which provides you with a more flexible, more controllable error-reporting mechanism anyway. I usually use exceptions when dealing with the outside world where I want to protect the outside world from any misbehaviour of the Haskell code. For example, if opening and closing windows or files, controlling a robot, etc., I might use the exception catching/propagating to make sure that things are shutdown properly when the Haskell program crashes. In general, if there's a way to make your program bombproof using a conventional mechanism (monads, Either, Maybe, etc.), then you should use it since it will be easier to reason about and can be more flexible. But, if your program really has to be bombproof and you can't convince yourself that it is (e.g., it depends on a library you didn't write), exception handling can help a lot. -- Alastair Reid

Two observations: 1. When I recently modified the HaXml XML parser, this is one of the significant changes I made: providing (alterantive) return values based on Either, so that input errors could be handled by the invoking function, without forcing it into the IO monad. I guess that's a vote of agreement. 2. I like to distinguish between "expected errors" and "unexpected errors". Having been burned in the past by using exceptions (not FP), I try to use them only for conditions that are truly unexpected; i.e. _exceptional_. Bad input, IMO, is something that is not unexpected, so I don't really like to handle that via exceptions. #g -- At 13:09 02/08/04 -0700, Evan LaForge wrote:
Exceptions are convenient in that you can rely on libraries throwing them instead of prechecking for valid values yourself (for instance, why check that the argument to Char.digitToInt is valid if digitToInt does so already), and you don't have to modify a lot of function signatures. Unfortunately, in the face of lazy evaluation, they can get thrown in unexpected places. Even with Exception.evaluate, or code which is already explicitly sequenced in the IO monad, like "t <- Exception.evaluate (map Char.digitToInt ['a'..'g'])" an exception will only be thrown when you inspect the last element of 't'. (digitToInt confusingly accepts hex "digits"---shouldn't it be "higitToInt" then?).
So it seems to me that if you are, say, checking input, the options are to handle exceptions from the checking code but expect them to come from the processing code, or decorate all checking code with Rights and Lefts. Problems with the first option are that checking code could also trigger some exceptions, and Prelude functions throw undescriptive errors like "user error" or low level ones like "refuted pattern match" and catching them over the entire program means you could stifle a lot of real errors. This implies that you have to make specific exceptions and convert Prelude and library exceptions into yours as low down as possible, which is cluttering but maybe not as cluttering as Either. Problems with the second option are many of the problems that lead to us wanting exceptions in the first place.
Using Either seems much simpler and functional-friendly. So then, in what contexts are exceptions appropriate in haskell? _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
------------ Graham Klyne For email: http://www.ninebynine.org/#Contact

Graham Klyne
2. I like to distinguish between "expected errors" and "unexpected errors". Having been burned in the past by using exceptions (not FP), I try to use them only for conditions that are truly unexpected; i.e. _exceptional_. Bad input, IMO, is something that is not unexpected, so I don't really like to handle that via exceptions.
I agree, trying to handle exceptions caused by incorrect input is just needless complication, the program should crash, and the calling function should be fixed instead. Common errors that happen to me are: Prelude.head : empty list Prelude.read : no parse and Prelude.(!!) : index too large and so on. Is there any easy way (TH?) to amend these to output the line number of the offending caller? It would be a great improvement to see something like Prelude.head : empty list in Foo.hs, line 4711 since programs generally contain many, many calls to functions like these. -kzm -- If I haven't seen further, it is by standing in the footprints of giants

Prelude.head : empty list Prelude.read : no parse and Prelude.(!!) : index too large and so on.
Is there any easy way (TH?) to amend these to output the line number of the offending caller?
In a program of any size, I usually avoid using these functions and instead define functions like: --| 1st arg is error message for empty lists head' :: Doc -> [a] -> a --| 1st arg is description of kind of thing in list -- Reports error if length of list /= 1 unique :: Doc -> [a] -> a etc. Another approach is to use a function: inContext :: String -> a -> a (implemented using mapException) like this: inContext "evaluating expression" (eval env e) to transform an exception of the form: error "Division by zero" into error "Division by zero while evaluating expression" Since the inContext function is rather like ghc's profiling function 'scc', it would be nice if ghc had a command-line flag to insert a call to inContext everywhere that it inserts a call to scc when you use -prof-all. (Of course, you'd probably take a big stack-space hit from doing so since the chain of calls to inContext is equivalent to the stack you would expect if using call by value without tail call optimization. -- Alastair Reid

On 03-Aug-2004, Alastair Reid
Another approach is to use a function:
inContext :: String -> a -> a
(implemented using mapException) like this:
inContext "evaluating expression" (eval env e)
to transform an exception of the form:
error "Division by zero"
into
error "Division by zero while evaluating expression"
As written, that won't work properly; you will get misleading error messages, due to lazy evaluation. If a division by zero exception is thrown, it may have occurred when computing the expression e, rather than in the function eval itself. Furthermore, if eval returns a compound type, this code won't catch all division by zero errors that occur while computing eval, only those that occur while computing the top-most node. To make it work properly, you can do something like class hyperSeq a where hyperSeq :: a -> b -> b inContext :: (hyperSeq a, hyperSeq b) => String -> (a -> b) -> a -> b and then inContext can force full evaluation of the input and output, making sure to force full evaluation of the input _before_ invoking mapException: inContext msg fn input = hyperSeq input $ mapException (...) $ let output = fn input in hyperSeq output output Of course then you rather completely lose lazy evaluation. But who wants lazy evaluation, anyway? :) -- Fergus J. Henderson | "I have always known that the pursuit Galois Connections, Inc. | of excellence is a lethal habit" Phone: +1 503 626 6616 | -- the last words of T. S. Garp.

On Tue, Aug 03, 2004 at 12:51:50PM +0200, Ketil Malde wrote:
Is there any easy way (TH?) to amend these to output the line number of the offending caller? It would be a great improvement to see something like
Prelude.head : empty list in Foo.hs, line 4711
since programs generally contain many, many calls to functions like these.
I include the following file in most files (and always use the C preprocessor): import DarcsUtils ( bug ) #define impossible (bug $ "Impossible case at "++__FILE__++":"++show (__LINE__ :: Int)++" compiled "++__TIME__++" "++__DATE__) #define fromJust (\m -> case m of {Nothing -> bug ("fromJust error at "++__FILE__++":"++show (__LINE__ :: Int)++" compiled "++__TIME__++" "++__DATE__); Just x -> x}) Here "bug" is a function that just calls "error" with a little prefix explaining that there is a bug in darcs, and would the user please report it. Obviously, defining a head here would be just as easy, but I usually have more trouble with fromJust, which in a few places gets called in internal routines where I *should* know that the data structure has no Nothings, but have been known to make mistakes. I also catch all "error" exceptions and print a nicer error message, so I use fail and error "raw" to indicate actual user errors. -- David Roundy http://www.abridgegame.org

David Roundy
Here "bug" is a function that just calls "error" with a little prefix explaining that there is a bug in darcs, and would the user please report it. Obviously, defining a head here would be just as easy,
Cool! The basic trick is just to inline the actual function defintions using CPP macros. I've made macros for most of the troublesome functions, but I can't get it to work for operators (something like `(!!)` doesn't parse, it seems) Any tricks? Unless I'm overlooking something, I could have a file prelude.h containing something like: ----8<------ import Prelude hiding (head,(!!),read) #define head (\xs -> case xs of { (x:_) -> x ; _ -> bug "head" __FILE__ __LINE__}) #define at (let {at (y:_) 0 = y; at (y:ys) n = at ys (n-1); at _ _ = bug "at" __FILE__ __LINE__} in \a x -> at a x) #define read (\s -> case [ x | (x,t) <- reads s, ("","") <- lex t] of { [x] -> x ; _ -> bug "read" __FILE__ __LINE__}) #define fromJust (\x -> case x of Just a -> a; Nothing -> bug "fromJust" __FILE__ __LINE__) bug c f l = error ("Program error - illegal parameters to '"++c++"', file '"++f++"', line "++show l) ----8<------ and just #include "prelude.h" if/when I want better debugging. No expensive stack frames, no unwanted strictness, and no clutter. Any comments? -kzm -- If I haven't seen further, it is by standing in the footprints of giants

Ketil Malde
Unless I'm overlooking something
Which I of course did.
#define at (let {at (y:_) 0 = y; at (y:ys) n = at ys (n-1); at _ _ = bug "at" __FILE__ __LINE__} in \a x -> at a x)
No prize for spotting the bug here. -kzm -- If I haven't seen further, it is by standing in the footprints of giants

I always thought it would be a good feature if functions inlined via {-# INLINE #-} could 'get at' their inlining point. like {-# INLINE head #-} head (x:_) = x head _ = error $ {-# ERRLOC #-} "head: empty list" the semantics being that when inlining {-# ERRLOC #-} is rewritten to (\s -> "Foo.hs:24: " ++ s) (with Foo.hs and 24 replaced by the inlining point). Note that this is designed so that if the compiler does not support ERRLOC or head could not be inlined for some reason, it degrades gracfully. John -- John Meacham - ⑆repetae.net⑆john⑈

Ketil Malde
import Prelude hiding (head,(!!),read)
Any comments?
Here's one: I thought this would make it difficult to have other imports of Prelude, hiding other pieces of it (e.g. catch, to avoid ambiguities with Control.Exception.catch) (Also, the definition of 'bug' hinders forces the #include to be after any imports, not sure it's better to have a separate module for it, which would then need to be on the module search path) (I seem to be mainly following up to my own posts, so if somebody asks, I'll take this to a quiet corner at the -cafe :-) -kzm -- If I haven't seen further, it is by standing in the footprints of giants

Hi - I am just learning Haskell and am far away from exception handling intricacies. However I just recently read an article of Herb Sutter about exception handling in C++ with some rules when to use exception handling - and perhaps these rules might be applicable to Haskell too (article: "When and How to Use Exceptions", Herb Sutter, C/C++ Users Journal August 2004, pp. 47 -- 51)? Herb Sutter gave these rules : An error is any failure that prevents a function from succeeding. Three main kind of errors: - a condition that prevents the function from meeting a precondition of another function that must be called (so to say: the caller has to check for preconditions while the function called my use C/C++ asserts to assert the correctness of its preconditions) - a condition that prevents a function from establishing one of its own postconditions (e.g. producing a (valid) return value) - a condition that prevents the function from reestablishing an invariant that it is responsible to maintain (special kind of postcondition mainly found in object oriented code). Any other condition is not an error and shouldn't be reported as one. The code that could cause an error is responsible for detecting and reporting the error otherwise this is a programming mistake. I am not sure if these rules apply to real functional programming but at least they seem to be useable. The point that the caller is responsible for checking the preconditions of functions it is calling is something I also found as a suggestion in using the object oriented language Eiffel. Cheers and best regards Bjoern

W liście z wto, 03-08-2004, godz. 13:05 +0200, Bjoern Knafla napisał:
Herb Sutter gave these rules :
An error is any failure that prevents a function from succeeding. Three main kind of errors: [...]
These kinds don't explain much. They don't give a clue which errors to report by exceptions and which by return values, because they don't distinguish the reasons for which a (pre|post)condition can't be fulfilled. And I see no point in distinguishing potential errors on this axis. I divide exceptional situations into 4 groups: 1. Expected error: The result can't be computed from this data; the caller was not supposed to check this beforehand because it would duplicate work of the function, or there would be a race condition, or the design is simpler that way. 2. Program error: The given function was not supposed to be invoked in this way by other parts of the program (with these arguments, or with this external state, in this order wrt. other functions etc.). 3. Out of resource: The function is sorry that it was not able to produce the result due to limited memory or arithmetic precision or similar resources. "Not yet supported" is also in this group. 4. Impossible error: The function thought this couldn't happen, it must have a bug. This is like 2 in that there is a bug somewhere, but like 1 in that the calling code is probably fine. I/O errors can be expected errors or out of resource conditions, depending on whether they were caused by bad input or by external failure. Group 1 should be reported in Haskell by a distinguished result (Maybe, Either, custom type) or by an exception passed in a suitable monad (well, Maybe is a monad; this could be even the IO monad). Other groups are handled in a similar way. In Haskell they can be reported by a bottom, e.g. 'error' function. It makes sense to catch them on a toplevel of the program or a large unit, and it's good that GHC provides a way to catch them. There may be other actions possible than just aborting the program: report the problem to the user, abort just the given operation, but try to continue other work; save user data before aborting; log the error or report it in an appropriate channel, e.g. output a HTML page in case of a CGI program. Sometimes the same function makes sense in two variants: one which reports an expected error, and another which treats it as a program error. Example: lookup in a dictionary. Sometimes the qualification of an error changes while it is propagated. I haven't thought about all cases, but for example program errors become impossible errors if the caller thought that the arguments it passed were good, and expected errors become program errors if the caller's caller was not supposed to give arguments which cause such problems. The most important distinction is between expected errors, whose way of reporting should be visible in the interface and which should be checked for in the calling code, and program errors, which should be automatically propagated and reported near the toplevel. -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/
participants (9)
-
Alastair Reid
-
Bjoern Knafla
-
David Roundy
-
Evan LaForge
-
Fergus Henderson
-
Graham Klyne
-
John Meacham
-
Ketil Malde
-
Marcin 'Qrczak' Kowalczyk