Re: [Haskell-cafe] Re: Exception handling in numeric computations

On Wed, Mar 25, 2009 at 7:02 PM, Gregory Petrosyan
First of all, thanks everybody for the discussion -- very interesting to read!
Apologies if this is off-topic a bit.
While reading, I have a feeling that proposed solutions are somewhat similar to checked exceptions. And IMO they turned out to be more harmful than useful.
Do you mean checked exceptions, or the proposed solutions? IMO the problem with checked exceptions is that they conflate two ideas by trying to use the existing exception framework to do something that can't otherwise be done by the language. This leads to something that doesn't work well for either use. Languages with checked exceptions usually use them for two purposes: 1. Exceptional conditions - disk full, CPU on fire, etc. 2. Error handling - invalid arguments to a function, attempt to invert non-invertible matrix, etc. In the first case, checked exceptions are a pain because there are so many different possible exceptional conditions to enumerate. Or alternatively you have an IOException that can be one of hundreds of actual exceptional conditions. This is exactly what regular exceptions are for, and in general there's little that can be done except terminate the program, except for certain special cases explicitly handled by the programmer. For the second case, checked exceptions actually work okay IMO. The syntax is a bit clunky, but they serve the purpose. That is, they specifically indicate what functions may fail and the failure mode. They also allow calling code to either handle it there or pass the error up to a higher level as appropriate. The syntax is usually ugly, though. Combining these two functions into one tool gives suboptimal results. If you're using checked exceptions for error handling, then you also end up using them for exception handling. That means you have IOException thrown by just about everything (and in theory any function in an imperative language could have an IOException due to hardware fault), leading to complex exception handlers with a half dozen case statements. There are a few reasons why Haskell's approach is (or at least can be) different. Compare the error handling models. Checked exceptions are bolted on to an existing tool attempting to make it serve a different purpose. It seems like a good idea, but ends up being painful because the two uses are at cross purposes. Instead of using a specific implementation and trying to alter it for another use, Haskell uses a very general facility, the type system, to solve one problem, and keeps the exception handling facility separate. Haskell's syntax for error handling is also much nicer. Nobody likes using methods that could possibly throw a dozen different exceptions, some of which are IOException and some are not. Actually, Haskell IO makes a big difference to this. Conceptually, you could think of Haskell functions as purely mathematical constructs. In particular, they can't interact with the physical world. This is true even for the IO monad. Exceptional conditions only arise when the Haskell runtime actually tries to *evaluate* an IO function. As a consequence exceptional conditions only arise as part of IO actions, and can only be handled by IO actions (because they're interacting with the run-time environment). This distinction makes it possible to enforce a separation between exceptions and error conditions, which is generally not possible in imperative languages where a function could fail either for a computational reason (divide by 0) or a hardware/runtime fault. Even in Haskell this separation isn't absolute. Programmer errors, such as dividing by 0, can and do lead to exceptional conditions. The proper way to handle dividing by 0 is to not do it in the first place, but if it happens because of a programming error, you've got an exception. Unfortunately this encourages programmers to think that handling the exception is the proper way to deal with this condition, but it isn't. I've only recently come around to the camp of treating exception handling and errors separately, so some of these thoughts may be a bit loose for the moment. In particular my thoughts from the above paragraph have only recently become clear. Henning T., FYI your constant advocacy has gotten at least one person around to this view. Cheers, John Lato

John Lato
Even in Haskell this separation isn't absolute. Programmer errors, such as dividing by 0, can and do lead to exceptional conditions. The proper way to handle dividing by 0 is to not do it in the first place, but if it happens because of a programming error, you've got an exception. Unfortunately this encourages programmers to think that handling the exception is the proper way to deal with this condition, but it isn't.
So I have another question. Is the following function safe and legitimate?
safeDiv :: (Exception e, Integral a) => a -> a -> Either e a safeDiv x y = unsafePerformIO . try . evaluate $ div x y
I believe it should be okay to use this 'safeDiv'. What do you think? -- c/* __o/* <\ * (__ */\ <

On Thu, 26 Mar 2009, Xiao-Yong Jin wrote:
So I have another question. Is the following function safe and legitimate?
safeDiv :: (Exception e, Integral a) => a -> a -> Either e a safeDiv x y = unsafePerformIO . try . evaluate $ div x y
I believe it should be okay to use this 'safeDiv'. What do
I think that question is wrong way around. The real question is, why do you want to solve your problem using unsafePerformIO?

Henning Thielemann
On Thu, 26 Mar 2009, Xiao-Yong Jin wrote:
So I have another question. Is the following function safe and legitimate?
safeDiv :: (Exception e, Integral a) => a -> a -> Either e a safeDiv x y = unsafePerformIO . try . evaluate $ div x y
I believe it should be okay to use this 'safeDiv'. What do
I think that question is wrong way around. The real question is, why do you want to solve your problem using unsafePerformIO?
I just want to know, from a theoretical point of view, whether this 'safeDiv' in above definition is the same as
safeDiv' :: (Exception e, Integral a) => a -> a -> Either e a safeDiv' _ 0 = Left e safeDiv' x y = Right $ div x y
For the question why do I want to do that, I am not sure. I guess if the function which has an error call inside is provided by other library package, and I don't have a clear and easy way to tell whether the function will make the error call or not, it would be easy just to make a wrapper like that. It's also a possible situation that I don't know how to test the input to a foreign function call. -- c/* __o/* <\ * (__ */\ <

On Thu, 2009-03-26 at 14:23 -0400, Xiao-Yong Jin wrote:
Henning Thielemann
writes: On Thu, 26 Mar 2009, Xiao-Yong Jin wrote:
So I have another question. Is the following function safe and legitimate?
safeDiv :: (Exception e, Integral a) => a -> a -> Either e a safeDiv x y = unsafePerformIO . try . evaluate $ div x y
I believe it should be okay to use this 'safeDiv'. What do
I think that question is wrong way around. The real question is, why do you want to solve your problem using unsafePerformIO?
I just want to know, from a theoretical point of view, whether this 'safeDiv' in above definition is the same as
safeDiv' :: (Exception e, Integral a) => a -> a -> Either e a safeDiv' _ 0 = Left e safeDiv' x y = Right $ div x y
You need some sort of type case here to make sure your first case matches only if e is the right type for divide-by-zero errors (too lazy to look it up atm). Alternatively, you could replace your type variable e with the actual exception type you want, here and in the unsafePerformIO version. Other than that, I think the imprecise exceptions paper guarantees that these two functions are equivalent (albeit unwisely: see below).
For the question why do I want to do that, I am not sure. I guess if the function which has an error call inside is provided by other library package, and I don't have a clear and easy way to tell whether the function will make the error call or not, it would be easy just to make a wrapper like that.
It might be easy, but if you didn't have a lot of insight into the function's behavior, then it would be difficult to tell whether it's really going to call error or whether it's going to go off into an infinite loop. (Consider the (slow) definition x ^ n | n == 0 = 1 | n < 0 = error "Negative exponents require ^^" | otherwise = x * x ^ (n - 1) Now consider what happens if the library function forgets the second case. Your wrapper isn't safe anymore!) I can see only two cases where a library function could call error sometimes, and you wouldn't have a good feel for when: a) The function is calling error on exceptions. You should bug the library author to put the function into an exception monad instead. Devil-may-care users can use either (error . show) id to turn exceptions into errors. b) The function has explicit pre-conditions, which you don't understand. You shouldn't pass arguments to a function that violate its pre-conditions (ever!); if you don't understand those preconditions well enough to test them in Haskell code, you might not understand them well enough to make sure your code is calling the function correctly. So you might want to study the preconditions a little more.
It's also a possible situation that I don't know how to test the input to a foreign function call.
FFI calls cannot throw Haskell exceptions. jcc

Jonathan Cast wrote:
Xiao-Yong Jin wrote:
Xiao-Yong Jin wrote:
So I have another question. Is the following function safe and legitimate?
safeDiv :: (Exception e, Integral a) => a -> a -> Either e a safeDiv x y = unsafePerformIO . try . evaluate $ div x y
safeDiv' :: (Exception e, Integral a) => a -> a -> Either e a safeDiv' _ 0 = Left e safeDiv' x y = Right $ div x y
[...] Other than that, I think the imprecise exceptions paper guarantees that these two functions are equivalent (albeit unwisely: see below).
I don't think so. The evaluation of x and y may throw errors before we get around to div. * safeDiv' will evaluate y (to pattern match against 0) and may return an error, e, whereas safeDiv will return Left e if div is strict in y. * safeDiv' postpones evaluating x and so may return Right e, whereas safeDiv will return Left e if div is strict in x. -- Live well, ~wren

On Thu, 2009-03-26 at 21:57 -0400, wren ng thornton wrote:
Jonathan Cast wrote:
Xiao-Yong Jin wrote:
Xiao-Yong Jin wrote:
So I have another question. Is the following function safe and legitimate?
safeDiv :: (Exception e, Integral a) => a -> a -> Either e a safeDiv x y = unsafePerformIO . try . evaluate $ div x y
safeDiv' :: (Exception e, Integral a) => a -> a -> Either e a safeDiv' _ 0 = Left e safeDiv' x y = Right $ div x y
[...] Other than that, I think the imprecise exceptions paper guarantees that these two functions are equivalent (albeit unwisely: see below).
I don't think so. The evaluation of x and y may throw errors before we get around to div.
Sure. Which also points out that the original safeDiv wasn't actually safe, since there's no guarantee of what evaluate will do with x and y. (Actually, there's not much guarantee of what evaluate does anyway --- just that any errors in e's definition get turned into exceptions by the time evaluate e finishes running, or don't turn into exceptions at all).
* safeDiv' will evaluate y (to pattern match against 0) and may return an error, e, whereas safeDiv will return Left e if div is strict in y.
* safeDiv' postpones evaluating x and so may return Right e, whereas safeDiv will return Left e if div is strict in x.
jcc

Jonathan Cast wrote:
Sure. Which also points out that the original safeDiv wasn't actually safe, since there's no guarantee of what evaluate will do with x and y. (Actually, there's not much guarantee of what evaluate does anyway --- just that any errors in e's definition get turned into exceptions by the time evaluate e finishes running, or don't turn into exceptions at all).
That is not true if you mean "any errors" as "any and all errors". The 'evaluate' operation only forces the argument into weak head normal form (WHNF) not normal form (NF). Thus 'evaluate' may succeed and then its return value could be examined further and only then trigger an exception. I could easily define a new "Integral" type where WHNF is not NF and demonstrate the problem. The solution is to use 'evaluate' only on known primitive types like Int, or on polymorphic data constrained to be NFDATA and use the 'rnf' strategy within the call to 'evaluate'. -- Chris

On Fri, 2009-03-27 at 12:24 +0000, Chris Kuklewicz wrote:
Jonathan Cast wrote:
Sure. Which also points out that the original safeDiv wasn't actually safe, since there's no guarantee of what evaluate will do with x and y. (Actually, there's not much guarantee of what evaluate does anyway --- just that any errors in e's definition get turned into exceptions by the time evaluate e finishes running, or don't turn into exceptions at all).
That is not true if you mean "any errors" as "any and all errors".
No, that's not what I mean. I just couldn't think of a good phrasing for `errors that would prevent e from being evaluated to HNF'. Which is still a lousy phrasing. jcc

safeDiv :: (Exception e, Integral a) => a -> a -> Either e a safeDiv x y = unsafePerformIO . try . evaluate $ div x y
I just want to know, from a theoretical point of view, whether this 'safeDiv' in above definition is the same as
safeDiv' :: (Exception e, Integral a) => a -> a -> Either e a safeDiv' _ 0 = Left e safeDiv' x y = Right $ div x y
No. Firstly, safeDiv' doesn't compile!-) Then, if you replace 'e' by 'DivideByZero' and adjust the types: *Main> safeDiv 1 (throw Overflow) Left arithmetic overflow *Main> safeDiv' 1 (throw Overflow) *** Exception: arithmetic overflow Try ':info ArithException' for more in the same group. You could use other functions in Control.Exceptions to get more control about which exceptions you want to handle and how, but so far, there is no indication that 'unsafePerformIO' is the right hammer to use here.. Claus -- unsagePerformIO: some things are just not wise to do

Thanks a lot for the answer!
On Thu, Mar 26, 2009 at 4:36 PM, John Lato
Languages with checked exceptions usually use them for two purposes:
1. Exceptional conditions - disk full, CPU on fire, etc. 2. Error handling - invalid arguments to a function, attempt to invert non-invertible matrix, etc.
Is there any good rule someone can use to decide whether it is error or exception? For me, this is the most important thing, because IMHO you (as library writer) often can't say what is it, it's up to client of your code to decide.
Henning T., FYI your constant advocacy has gotten at least one person around to this view.
Can you please provide me some links about error/exception separation? Gregory

On Fri, Mar 27, 2009 at 10:01 AM, Gregory Petrosyan
Thanks a lot for the answer!
On Thu, Mar 26, 2009 at 4:36 PM, John Lato
wrote: Languages with checked exceptions usually use them for two purposes:
1. Exceptional conditions - disk full, CPU on fire, etc. 2. Error handling - invalid arguments to a function, attempt to invert non-invertible matrix, etc.
Is there any good rule someone can use to decide whether it is error or exception? For me, this is the most important thing, because IMHO you (as library writer) often can't say what is it, it's up to client of your code to decide.
An exception is caused by some sort of interaction with the run-time system (frequently a hardware issue). The programmer typically can't check for these in advance, but can only attempt to recover after they've happened. An error is some sort of bug that should be fixed by the programmer. There is some overlap for certain cases, notably divide by 0. Dividing by 0 is an error, because it's something that the program should never do, and it can be detected and dealt with by the programmer in advance. However most systems allow the divide function to be called with a 0 denominator. The function has a precondition, meaning the onus is on the programmer to not do it, however this is not enforced by the language. If a program does this in error, the result is an exception because the result is not a valid number (this is codified with NaN for IEEE floats). In this case, a programming error results in an exception. The proper solution is to fix the source of the problem, the error, instead of trying to clean up the results.
Henning T., FYI your constant advocacy has gotten at least one person around to this view.
Can you please provide me some links about error/exception separation?
http://haskell.org/haskellwiki/Error http://haskell.org/haskellwiki/Exception

Quoth John Lato
An exception is caused by some sort of interaction with the run-time system (frequently a hardware issue). The programmer typically can't check for these in advance, but can only attempt to recover after they've happened.
An error is some sort of bug that should be fixed by the programmer.
I have never felt that I really understood that one. What about invalid inputs? Say someone encounters a disk full error, and the resulting partly written file is now unreadable data for its intended application because of an invalid file encoding? Is that an exception, or a bug that should be fixed? My guess is that you'll say it's a bug, i.e., that application's file decoding result should be an Either type that anticipates that the file encoding may be invalid. I will also guess if the file is unreadable because of an external I/O problem like no read access to file or filesystem, you would similarly expect this to be treated like that - I mean, ideally, e.g., hGetLine :: Handle -> IO (Either IOError String) Does that make sense so far? Donn

On Fri, 2009-03-27 at 09:31 -0700, Donn Cave wrote:
Quoth John Lato
, An exception is caused by some sort of interaction with the run-time system (frequently a hardware issue). The programmer typically can't check for these in advance, but can only attempt to recover after they've happened.
An error is some sort of bug that should be fixed by the programmer.
I have never felt that I really understood that one.
What about invalid inputs? Say someone encounters a disk full error, and the resulting partly written file is now unreadable data for its intended application because of an invalid file encoding? Is that an exception, or a bug that should be fixed?
NB: Of course it's a bug: if the disk is full, the partially written file should be discarded and the previous version retained. I'm not going to hold you accountable for Unix's bugs, though.
My guess is that you'll say it's a bug,
I think you mean `exception' here.
i.e., that application's file decoding result should be an Either type that anticipates that the file encoding may be invalid.
This is pretty standard, I thought. Do people write Haskell file input methods that are undefined (`throw exceptions') on invalid inputs (e.g., do people use read to parse input from users or the file system[1])?
I will also guess if the file is unreadable because of an external I/O problem like no read access to file or filesystem, you would similarly expect this to be treated like that - I mean, ideally, e.g., hGetLine :: Handle -> IO (Either IOError String)
IO is an exception monad already. I don't think there's an objection to throwing exceptions with throwIO and catching them in IO; my objection, at least, is to designing your program to throw exceptions from (ostensibly...) *pure* code and catch those in IO, in a live environment.
Does that make sense so far?
jcc [1] This post should not be taken as an endorsement of the use of the Read class for any purpose, nor as an endorsement of its continued existence in the standard library.

Jonathan Cast schrieb:
i.e., that application's file decoding result should be an Either type that anticipates that the file encoding may be invalid.
This is pretty standard, I thought. Do people write Haskell file input methods that are undefined (`throw exceptions') on invalid inputs (e.g., do people use read to parse input from users or the file system[1])?
With case reads str of [(x, "")] -> Just x _ -> Nothing you are safe. (I think it's now available as maybeRead.) In general, relying on a well-formed input file is an error. However, if your program detects a format error in file input, it could throw an exception. But this means that your program must be prepared for these problems.
I will also guess if the file is unreadable because of an external I/O problem like no read access to file or filesystem, you would similarly expect this to be treated like that - I mean, ideally, e.g., hGetLine :: Handle -> IO (Either IOError String)
IO is an exception monad already. I don't think there's an objection to throwing exceptions with throwIO and catching them in IO; my objection, at least, is to designing your program to throw exceptions from (ostensibly...) *pure* code and catch those in IO, in a live environment.
Actually, I really object to have exception handling built into IO monad. Especially with extensible-exceptions package you can hide which kinds of exceptions can occur in a piece of code, which is a bad thing. When it comes to lazy I/O, which is problematic in itself, it is better to have explicit exceptions (i.e. IO (Either IOError String)) on top of exception-free IO. See the recent thread on safe lazy I/O: http://www.haskell.org/pipermail/haskell-cafe/2009-March/058205.html

Henning Thielemann
Actually, I really object to have exception handling built into IO monad.
I couldn't agree more. If I want to write non-recovering code, I can always just say (Right foo) <- readLine , and hope that the RTS is smart enough to print the error message in the left constructor should that match fail. -- (c) this sig last receiving data processing entity. Inspect headers for copyright history. All rights reserved. Copying, hiring, renting, performance and/or quoting of this signature prohibited.

On Sat, 2009-03-28 at 01:27 +0100, Henning Thielemann wrote:
Jonathan Cast schrieb:
i.e., that application's file decoding result should be an Either type that anticipates that the file encoding may be invalid.
This is pretty standard, I thought. Do people write Haskell file input methods that are undefined (`throw exceptions') on invalid inputs (e.g., do people use read to parse input from users or the file system[1])?
With
case reads str of [(x, "")] -> Just x _ -> Nothing
you are safe. (I think it's now available as maybeRead.)
Hmm, hoogle doesn't know that name.
In general, relying on a well-formed input file is an error. However, if your program detects a format error in file input, it could throw an exception. But this means that your program must be prepared for these problems.
Right.
I will also guess if the file is unreadable because of an external I/O problem like no read access to file or filesystem, you would similarly expect this to be treated like that - I mean, ideally, e.g., hGetLine :: Handle -> IO (Either IOError String)
IO is an exception monad already. I don't think there's an objection to throwing exceptions with throwIO and catching them in IO; my objection, at least, is to designing your program to throw exceptions from (ostensibly...) *pure* code and catch those in IO, in a live environment.
Actually, I really object to have exception handling built into IO monad. Especially with extensible-exceptions package you can hide which kinds of exceptions can occur in a piece of code, which is a bad thing. When it comes to lazy I/O, which is problematic in itself, it is better to have explicit exceptions (i.e. IO (Either IOError String)) on top of exception-free IO. See the recent thread on safe lazy I/O: http://www.haskell.org/pipermail/haskell-cafe/2009-March/058205.html
Yi's new parsing library (just finished the paper a couple days ago) seems quite appropriate to a lazy IO library; for that library, partial grammars are errors anyway, so the issue doesn't really arise. If you want IO failure/parsing failure handling in lazy IO, my preference would be for a separate failure-handling hook (which can throw an asynchronous exception if needed) rather than for any kind of synchronous exception mechanism per se. There's really not much you can do, except tell the user either `hey, that doesn't look like valid input!' or `sorry, the other side of the network connection disappeared', and hope the operator can correct the issue. I don't think it's really important to let the input processing (as opposed to parsing) code handle the situation specifically. jcc

Jonathan Cast
i.e., that application's file decoding result should be an Either type that anticipates that the file encoding may be invalid.
This is pretty standard, I thought. Do people write Haskell file input methods that are undefined (`throw exceptions') on invalid inputs (e.g., do people use read to parse input from users or the file system[1])?
I often write parsers that either run successfully, or abort with an exception. I could of course return an Either type, but that would mean the whole file would need to be parsed before any results could be returned at all - which is a showstopper for streaming processing of large files. Since at least my files are typically machine generated, a parse error is either a programmer error in my parser, a programmer error in the generating program, or an operator error (viz. the user running the program on a completely different file type). In any case, I want the program execution to halt and report the error as soon as possible. So the difference between an exception or an error type is mainly what you intend to do about it. There's no point in wrapping divisions in Maybe unless you actually are able to do something useful to recover from a zero denominator. -k -- If I haven't seen further, it is by standing in the footprints of giants

On Sat, 28 Mar 2009, Ketil Malde wrote:
Jonathan Cast
writes: i.e., that application's file decoding result should be an Either type that anticipates that the file encoding may be invalid.
This is pretty standard, I thought. Do people write Haskell file input methods that are undefined (`throw exceptions') on invalid inputs (e.g., do people use read to parse input from users or the file system[1])?
I often write parsers that either run successfully, or abort with an exception. I could of course return an Either type, but that would mean the whole file would need to be parsed before any results could be returned at all - which is a showstopper for streaming processing of large files.
If you need a lazy parser with error reporting, I suggest Control.Monad.Exception.Asynchronous from the explicit-exception package.
Since at least my files are typically machine generated, a parse error is either a programmer error in my parser, a programmer error in the generating program, or an operator error (viz. the user running the program on a completely different file type).
That's no excuse. You can never rely on proper file formatting since the user alter the file while you process it, delete it, or remove the disk it is stored on.

On Fri, Mar 27, 2009 at 7:31 PM, Donn Cave
Quoth John Lato
, An exception is caused by some sort of interaction with the run-time system (frequently a hardware issue). The programmer typically can't check for these in advance, but can only attempt to recover after they've happened.
An error is some sort of bug that should be fixed by the programmer.
I have never felt that I really understood that one.
Me too :-) BTW, John, how often do you encounter _hardware_ issues compared to "errors"? Is an "out of memory" thing an error or exception? You will say "exception, for sure", wouldn't you? :-) And if it is a result of applying known-to-be-very-memory-hungry algorithms to non-trivial input? Looks like programmer's error, doesn't it? And I think I can provide lots of similar examples. If there exists separation between errors and exceptions, it should be very strong and evident — otherwise "casual programmers" like myself will need to stare at the ceiling every time they write something to decide what suits best. Gregory

On Fri, 2009-03-27 at 20:38 +0300, Gregory Petrosyan wrote:
On Fri, Mar 27, 2009 at 7:31 PM, Donn Cave
wrote: Quoth John Lato
, An exception is caused by some sort of interaction with the run-time system (frequently a hardware issue). The programmer typically can't check for these in advance, but can only attempt to recover after they've happened.
An error is some sort of bug that should be fixed by the programmer.
I have never felt that I really understood that one.
Me too :-)
BTW, John, how often do you encounter _hardware_ issues compared to "errors"?
Can't speak for anyone else, but I usually encounter hardware issues just before I replace the hardware...
Is an "out of memory" thing an error or exception? You will say "exception, for sure", wouldn't you? :-)
No. GHC possesses an out-of-memory exception that (IIRC) it never throws, because it's simply not worth trying to recover from heap exhaustion. Maybe 20 years ago it was, but these days a program that manages to exhaust space is almost certainly either buggy or poorly optimized. An `error' is any condition where the correct response is for the programmer to change the source code :)
And if it is a result of applying known-to-be-very-memory-hungry algorithms to non-trivial input? Looks like programmer's error, doesn't it?
See above.
And I think I can provide lots of similar examples.
If there exists separation between errors and exceptions, it should be very strong and evident — otherwise "casual programmers" like myself will need to stare at the ceiling every time they write something to decide what suits best.
Protip: try pacing instead of staring at the ceiling. jcc

Quoth Jonathan Cast
An `error' is any condition where the correct response is for the programmer to change the source code :)
That's a broad category, that overlaps with conditions where there are one or more correct responses for the original program as well. If I throw exceptions within the type system, using IO or whatever, and at some later time observe that I have caught one that would have been better handled closer to its source, for example. I've already technically satisfied my requirement, since everything is in an exception monad, but the exception is still a bug. Or if I catch an non-IO error using Control.Exception.catch (if I were using ghc), and take advantage of the opportunity to release locks, post various notices, etc. - I evidently have a bug, but I also may need to have a programmed response for it. Donn

On Fri, 27 Mar 2009, Donn Cave wrote:
Quoth Jonathan Cast
, An `error' is any condition where the correct response is for the programmer to change the source code :)
That's a broad category, that overlaps with conditions where there are one or more correct responses for the original program as well.
If I throw exceptions within the type system, using IO or whatever, and at some later time observe that I have caught one that would have been better handled closer to its source, for example. I've already technically satisfied my requirement, since everything is in an exception monad, but the exception is still a bug.
I don't understand this one.
Or if I catch an non-IO error using Control.Exception.catch (if I were using ghc), and take advantage of the opportunity to release locks, post various notices, etc. - I evidently have a bug, but I also may need to have a programmed response for it.
The usual example against clear separation of exceptions and errors is the web server which catches 'error's in order to keep running. However, the web server starts its parts as threads, and whenever one thread runs into an 'error', it is terminated, just like an external shell program, that terminates with a segmentation fault. So, yes an error might be turned into an exception, but these are rare cases. In general it is hard or impossible to correctly clean up after an error, because the error occured due to something that you as programmer didn't respect. The "error handler" could well make things worse by freeing memory that is already deallocated and so on.

Henning Thielemann
The usual example against clear separation of exceptions and errors is the web server which catches 'error's in order to keep running. However, the web server starts its parts as threads, and whenever one thread runs into an 'error', it is terminated, just like an external shell program, that terminates with a segmentation fault. So, yes an error might be turned into an exception, but these are rare cases. In general it is hard or impossible to correctly clean up after an error, because the error occured due to something that you as programmer didn't respect. The "error handler" could well make things worse by freeing memory that is already deallocated and so on.
I don't see that as an argument against 'clear separation', really. Having _some_ way of dealing an error (from within a program), in special circumstances doesn't preclude clearly separating how it's done from exception handling. I always find it jarring when an HUnit test I've run tells me it encountered an 'exception', when I'm testing pure code (nevertheless I'd also find it annoying if the entire test run terminated because of a failed pattern match). With respect to the last point - isn't proving that a given program can't corrupt its own RTS possible, even in the presence of errors?

Quoth Henning Thielemann
On Fri, 27 Mar 2009, Donn Cave wrote:
Quoth Jonathan Cast
, An `error' is any condition where the correct response is for the programmer to change the source code :)
That's a broad category, that overlaps with conditions where there are one or more correct responses for the original program as well.
If I throw exceptions within the type system, using IO or whatever, and at some later time observe that I have caught one that would have been better handled closer to its source, for example. I've already technically satisfied my requirement, since everything is in an exception monad, but the exception is still a bug.
I don't understand this one.
A lame attempt to demonstrate that "condition where [a] correct response is to change the code" applies to too many cases to be useful. (And that there are no cases where [the only] correct response is to change the code.)
Or if I catch an non-IO error using Control.Exception.catch (if I were using ghc), and take advantage of the opportunity to release locks, post various notices, etc. - I evidently have a bug, but I also may need to have a programmed response for it.
The usual example against clear separation of exceptions and errors is the web server which catches 'error's in order to keep running. However, the web server starts its parts as threads, and whenever one thread runs into an 'error', it is terminated, just like an external shell program, that terminates with a segmentation fault. So, yes an error might be turned into an exception, but these are rare cases. In general it is hard or impossible to correctly clean up after an error, because the error occured due to something that you as programmer didn't respect. The "error handler" could well make things worse by freeing memory that is already deallocated and so on.
But you'd have to weigh that against the consequences of not continuing. I can certainly see the attraction of the exception monad treatment for anticipated errors. It might tend to obscure a central computation for the sake of handling a lot of weird little errors that may never actually be encountered, and doesn't it occasionally have a problem with making things strict that didn't already need to be? but at least it makes it easier to reason about the code in terms that include errors. And I'm using nhc98 lately, so no Control.Exception.catch for me, but if I had it, I'd use it, just like I wear a helmet when I ride my bicycle or motorcycle. Donn

On Fri, 2009-03-27 at 21:16 -0700, Donn Cave wrote:
Quoth Henning Thielemann
, On Fri, 27 Mar 2009, Donn Cave wrote:
Quoth Jonathan Cast
, An `error' is any condition where the correct response is for the programmer to change the source code :)
That's a broad category, that overlaps with conditions where there are one or more correct responses for the original program as well.
If I throw exceptions within the type system, using IO or whatever, and at some later time observe that I have caught one that would have been better handled closer to its source, for example. I've already technically satisfied my requirement, since everything is in an exception monad, but the exception is still a bug.
I don't understand this one.
A lame attempt to demonstrate that "condition where [a] correct response is to change the code"
Please don't mis-quote me. I said `the' correct response. Both programming and operating computers are goal-directed processes; an error is a situation where the program detects a bug such that it cannot make progress toward the current goal without the programmer going and fixing that bug. If you have a condition where there is something (useful...) you want to do within the context of the current source code, do not use an error to signal that condition. Use an exception.
applies to too many cases to be useful. (And that there are no cases where [the only] correct response is to change the code.)
I think Henning's response, and others, have adequately covered this. jcc
participants (13)
-
Achim Schneider
-
Chris Kuklewicz
-
Claus Reinke
-
Donn Cave
-
Gregory Petrosyan
-
Henning Thielemann
-
Henning Thielemann
-
John Lato
-
Jonathan Cast
-
Ketil Malde
-
Robert Greayer
-
wren ng thornton
-
Xiao-Yong Jin