Re: Exception handling in numeric computations

From: 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.
Honestly, me neither, until recently. I'm only barely starting to understand it, and I do think there's a great deal of overlap. Even if an error is a bug that can be fixed by the programmer, certain exceptional situations can also be fixed by the programmer by handling the exception, even if they can't be detected in advance. In general I would say that I agree with JCC's responses.
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.
Not necessarily. I would be satisfied with a Maybe :) I've encountered malformed data frequently enough that any decoders I write (and I have done so recently) would anticipate invalid data.
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)
Not necessarily, but possibly. The big difference, of course, is that decoding can be a pure operation, while reading never is. I personally wouldn't mind if hGetLine had the type you give. The way I see it, there are two advantages to exceptions in this case. The first is that it's very easy for exceptions to trickle up and be handled at a higher level. The second 'advantage' is that the programmer doesn't need to explicitly handle exceptions, whereas an Either would require at least a pattern match to use the resulting value. John

On Sat, 28 Mar 2009, John Lato wrote:
From: Donn Cave
I have never felt that I really understood that one.
Honestly, me neither, until recently. I'm only barely starting to understand it, and I do think there's a great deal of overlap. Even if an error is a bug that can be fixed by the programmer, certain exceptional situations can also be fixed by the programmer by handling the exception, even if they can't be detected in advance.
For example? Btw. not handling an exception is an error.
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)
Not necessarily, but possibly. The big difference, of course, is that decoding can be a pure operation, while reading never is.
I personally wouldn't mind if hGetLine had the type you give. The way I see it, there are two advantages to exceptions in this case. The first is that it's very easy for exceptions to trickle up and be handled at a higher level. The second 'advantage' is that the programmer doesn't need to explicitly handle exceptions, whereas an Either would require at least a pattern match to use the resulting value.
I'm afraid there is some confusion about what we mean with "exception". Do you only mean the thing that is silently handled in the IO monad? Is Left in Either an exception for you, too? In explicit-exception I call the corresponding constructor Exception, because that's what it is used for. I like to call all those things exceptions, because they are intended for the same purpose: Signalling exceptional situations that we cannot avoid in advance but that must be handled when they occur. You can use IO and its exceptions, I call them IO exceptions. It does not show in its types that and which exceptions can occur. Some people consider this an advantage, I consider this an disadvantage. You can use error codes or Either or even better Exceptional from the explicit-exception package, and Haskell is strong enough to treat these like exceptions in C++/Java/Modula-3 etc. because you can use their monad transformer variants ErrorT and ExceptionalT respectively. Those monad transformers allow automatical termination of a series of actions once an exceptional result is obtained. But since ErrorT and ExceptionalT are burned into the types, you cannot miss to handle them. So the most convenient type for hGetLine would be hGetLine :: Handle -> ErrorT IOError IO String

Excerpts from Henning Thielemann's message of Sat Mar 28 21:49:33 +0100 2009:
On Sat, 28 Mar 2009, John Lato wrote:
From: Donn Cave
I have never felt that I really understood that one.
Honestly, me neither, until recently. I'm only barely starting to understand it, and I do think there's a great deal of overlap. Even if an error is a bug that can be fixed by the programmer, certain exceptional situations can also be fixed by the programmer by handling the exception, even if they can't be detected in advance.
For example?
Btw. not handling an exception is an error.
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)
Not necessarily, but possibly. The big difference, of course, is that decoding can be a pure operation, while reading never is.
I personally wouldn't mind if hGetLine had the type you give. The way I see it, there are two advantages to exceptions in this case. The first is that it's very easy for exceptions to trickle up and be handled at a higher level. The second 'advantage' is that the programmer doesn't need to explicitly handle exceptions, whereas an Either would require at least a pattern match to use the resulting value.
I'm afraid there is some confusion about what we mean with "exception". Do you only mean the thing that is silently handled in the IO monad? Is Left in Either an exception for you, too? In explicit-exception I call the corresponding constructor Exception, because that's what it is used for. I like to call all those things exceptions, because they are intended for the same purpose: Signalling exceptional situations that we cannot avoid in advance but that must be handled when they occur. You can use IO and its exceptions, I call them IO exceptions. It does not show in its types that and which exceptions can occur. Some people consider this an advantage, I consider this an disadvantage. You can use error codes or Either or even better Exceptional from the explicit-exception package, and Haskell is strong enough to treat these like exceptions in C++/Java/Modula-3 etc. because you can use their monad transformer variants ErrorT and ExceptionalT respectively. Those monad transformers allow automatical termination of a series of actions once an exceptional result is obtained. But since ErrorT and ExceptionalT are burned into the types, you cannot miss to handle them. So the most convenient type for hGetLine would be hGetLine :: Handle -> ErrorT IOError IO String
By reading the documentation of 'hGetLine' [1] one can see that this function can throw only an EOF exception so why not give it a type like below? hGetLine :: Handle -> ErrorT EOF IO String Since one will have to handle the error case it would be better to treat only the possible cases, no? [1]: http://www.haskell.org/ghc/docs/latest/html/libraries/base/System-IO.html#v%... -- Nicolas Pouillard

Nicolas Pouillard wrote:
Excerpts from Henning Thielemann's message of Sat Mar 28 21:49:33 +0100 2009:
On Sat, 28 Mar 2009, John Lato wrote:
From: Donn Cave
I have never felt that I really understood that one. Honestly, me neither, until recently. I'm only barely starting to understand it, and I do think there's a great deal of overlap. Even if an error is a bug that can be fixed by the programmer, certain exceptional situations can also be fixed by the programmer by handling the exception, even if they can't be detected in advance. For example?
Btw. not handling an exception is an error.
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) Not necessarily, but possibly. The big difference, of course, is that decoding can be a pure operation, while reading never is.
I personally wouldn't mind if hGetLine had the type you give. The way I see it, there are two advantages to exceptions in this case. The first is that it's very easy for exceptions to trickle up and be handled at a higher level. The second 'advantage' is that the programmer doesn't need to explicitly handle exceptions, whereas an Either would require at least a pattern match to use the resulting value. I'm afraid there is some confusion about what we mean with "exception". Do you only mean the thing that is silently handled in the IO monad? Is Left in Either an exception for you, too? In explicit-exception I call the corresponding constructor Exception, because that's what it is used for. I like to call all those things exceptions, because they are intended for the same purpose: Signalling exceptional situations that we cannot avoid in advance but that must be handled when they occur. You can use IO and its exceptions, I call them IO exceptions. It does not show in its types that and which exceptions can occur. Some people consider this an advantage, I consider this an disadvantage. You can use error codes or Either or even better Exceptional from the explicit-exception package, and Haskell is strong enough to treat these like exceptions in C++/Java/Modula-3 etc. because you can use their monad transformer variants ErrorT and ExceptionalT respectively. Those monad transformers allow automatical termination of a series of actions once an exceptional result is obtained. But since ErrorT and ExceptionalT are burned into the types, you cannot miss to handle them. So the most convenient type for hGetLine would be hGetLine :: Handle -> ErrorT IOError IO String
By reading the documentation of 'hGetLine' [1] one can see that this function can throw only an EOF exception so why not give it a type like below?
hGetLine :: Handle -> ErrorT EOF IO String
Since one will have to handle the error case it would be better to treat only the possible cases, no?
I'm afraid the documentation is incomplete. hGetLine can also fail because e.g. the device it was reading from has been unplugged, or it was reading from a network filesystem and the network has gone down. And there might be yet more errors to be invented in the future, so I'm not sure it would be a good idea to reflect this level of detail in the type. Cheers, Simon

On Mon, 30 Mar 2009, Simon Marlow wrote:
Nicolas Pouillard wrote:
By reading the documentation of 'hGetLine' [1] one can see that this function can throw only an EOF exception so why not give it a type like below?
hGetLine :: Handle -> ErrorT EOF IO String
Since one will have to handle the error case it would be better to treat only the possible cases, no?
I'm afraid the documentation is incomplete. hGetLine can also fail because e.g. the device it was reading from has been unplugged, or it was reading from a network filesystem and the network has gone down. And there might be yet more errors to be invented in the future, so I'm not sure it would be a good idea to reflect this level of detail in the type.
Or the file could get read-protected. In principle I like to show the possible exceptions by types, but the point Simon raises is also important. I think IOError is good here and one must use the IOError analysis functions, to find out whether one can do something more specific in the case. For the default case there should be good possibilities to report the exception, e.g. translate it to the user language. For example when a file was not found, one can tell the user how to obtain it. But if the file is read-protected, a default message would suffice. The type IOError would still exclude an exception like "Parser failure". Thus explicit IOError is more descriptive than the implicit exceptions in IO monad today. It's still interesting how to handle sets of exceptions. If you use the exception type (Either ParserException IOError) then this is different from (Either IOError ParserException). I think we should use type classes for exceptions. Then you can use one type containing all exceptions in an application, but the type signature tells which exceptions are actually possible. E.g. parser :: ParserException e => ExceptionalT e ParserMonad a getLine :: IOException e => ExceptionalT e IO String fileParser :: (ParserException e, IOException e) => ExceptionalT e IO String

On Sat, Mar 28, 2009 at 9:49 PM, Henning Thielemann
On Sat, 28 Mar 2009, John Lato wrote:
From: Donn Cave
I have never felt that I really understood that one.
Honestly, me neither, until recently. I'm only barely starting to understand it, and I do think there's a great deal of overlap. Even if an error is a bug that can be fixed by the programmer, certain exceptional situations can also be fixed by the programmer by handling the exception, even if they can't be detected in advance.
For example?
A file not being written because of a permissions error. This can't be detected in advance due to effects from other processes, but it's a predictable enough exception that the programmer should handle it for IO. Handling a DivByZero exception when doing IO, however, is very likely wrong.
Btw. not handling an exception is an error.
Agreed generally. But some exceptions are likely in given contexts, others are not. I don't think it's necessary to handle every possible exception, just the ones that are likely and predictable for a given activity. Excluding generic "The impossible happened, file a bug report" handlers.
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)
Not necessarily, but possibly. The big difference, of course, is that decoding can be a pure operation, while reading never is.
I personally wouldn't mind if hGetLine had the type you give. The way I see it, there are two advantages to exceptions in this case. The first is that it's very easy for exceptions to trickle up and be handled at a higher level. The second 'advantage' is that the programmer doesn't need to explicitly handle exceptions, whereas an Either would require at least a pattern match to use the resulting value.
I'm afraid there is some confusion about what we mean with "exception". Do you only mean the thing that is silently handled in the IO monad?
Yes. I was comparing exceptions as they exist in IO in Haskell to the proposed hGetLine type.
Is Left in Either an exception for you, too?
No.
In explicit-exception I call the corresponding constructor Exception, because that's what it is used for. I like to call all those things exceptions, because they are intended for the same purpose: Signalling exceptional situations that we cannot avoid in advance but that must be handled when they occur. You can use IO and its exceptions, I call them IO exceptions. It does not show in its types that and which exceptions can occur. Some people consider this an advantage, I consider this an disadvantage.
I remain undecided on this for the moment. I should take another look at explicit-exception now that I understand its intent better. John

On Sun, 29 Mar 2009, John Lato wrote:
On Sat, Mar 28, 2009 at 9:49 PM, Henning Thielemann
wrote: On Sat, 28 Mar 2009, John Lato wrote:
Honestly, me neither, until recently. I'm only barely starting to understand it, and I do think there's a great deal of overlap. Even if an error is a bug that can be fixed by the programmer, certain exceptional situations can also be fixed by the programmer by handling the exception, even if they can't be detected in advance.
For example?
A file not being written because of a permissions error. This can't be detected in advance due to effects from other processes, but it's a predictable enough exception that the programmer should handle it for IO.
Indeed. However I still do not see the overlap of errors and exceptions. :-( Handling of exceptions may mean that you print a message and abort the affected operation, that is you often do not need a specific exception handling. E.g. if the user wants to load a file into an editor, and the file is read-protected, then the editor reports, that the file cannot be loaded and does not load it. The editor should keep running, in contrast to when it detects a programming error. The same should happen when the editor encounters a memory overflow. So I cannot follow the argument of "memory exhaustion shouldn't happen anymore today, so there is no need to handle it". I remember this was claimed in this thread, too. However, recovering from a memory exhaustion can be tricky. Can the garbage collector still free memory, when it cannot allocate memory temporarily?
Btw. not handling an exception is an error.
Agreed generally. But some exceptions are likely in given contexts, others are not. I don't think it's necessary to handle every possible exception, just the ones that are likely and predictable for a given activity.
Since handling of exceptions often consist of reporting the exception and aborting an operation, it should always be managable to handle all possible exceptions.
Excluding generic "The impossible happened, file a bug report" handlers.
This is the one special case, where program errors are catched in order to allow debugging.

On Mon, Mar 30, 2009 at 11:03 PM, Henning Thielemann
On Sun, 29 Mar 2009, John Lato wrote:
On Sat, Mar 28, 2009 at 9:49 PM, Henning Thielemann
wrote: On Sat, 28 Mar 2009, John Lato wrote:
Honestly, me neither, until recently. I'm only barely starting to understand it, and I do think there's a great deal of overlap. Even if an error is a bug that can be fixed by the programmer, certain exceptional situations can also be fixed by the programmer by handling the exception, even if they can't be detected in advance.
For example?
A file not being written because of a permissions error. This can't be detected in advance due to effects from other processes, but it's a predictable enough exception that the programmer should handle it for IO.
Indeed. However I still do not see the overlap of errors and exceptions. :-(
Really? You wrote in a prior email:
Btw. not handling an exception is an error.
An exception thrown at run-time reveals a programming error, and if the exception could not be thrown in this context then the error wouldn't exist either. The error is that the programmer did not deal with an exception. Conceptually I think the difference between errors and exceptions is clear, but many programming errors are a result of a lack or improper handling of exceptions. That's the overlap to which I refer.
Handling of exceptions may mean that you print a message and abort the affected operation, that is you often do not need a specific exception handling. E.g. if the user wants to load a file into an editor, and the file is read-protected, then the editor reports, that the file cannot be loaded and does not load it. The editor should keep running, in contrast to when it detects a programming error. The same should happen when the editor encounters a memory overflow. So I cannot follow the argument of "memory exhaustion shouldn't happen anymore today, so there is no need to handle it". I remember this was claimed in this thread, too. However, recovering from a memory exhaustion can be tricky. Can the garbage collector still free memory, when it cannot allocate memory temporarily?
I thought the argument was that "when memory exhaustion occurs in a Haskell application, it is (usually) the result of a programming error." This means handling the exception and trying to continue work is not the proper approach to dealing with memory exhaustion.
Btw. not handling an exception is an error.
Agreed generally. But some exceptions are likely in given contexts, others are not. I don't think it's necessary to handle every possible exception, just the ones that are likely and predictable for a given activity.
Since handling of exceptions often consist of reporting the exception and aborting an operation, it should always be managable to handle all possible exceptions.
Manageable, but possibly not useful for all functions. I don't believe that anyone's advocating that all IO functions are wrapped in a 'try' or 'bracket', so long as there are exception handlers at appropriate levels. John
participants (4)
-
Henning Thielemann
-
John Lato
-
Nicolas Pouillard
-
Simon Marlow