Re: [Haskell-cafe] Re: [Haskell] ANNOUNCE: control-monad-exception 0.5 with monadic call traces

Luke Palmer escribió:
On Fri, Nov 6, 2009 at 6:54 PM, klondike
wrote: Henning Thielemann escribió:
That's what I meant with my post: Programming errors (like "head []") are not handled by control-monad-exception. As far as I understand, control-monad-exception makes _exceptions_ explicit in the type signatures, not programming errors. Why and how would you make possible programming errors explicit in the type? But for exceptions (e.g. "file could not be found") a detailed stack trace is not of much use.
I think you have overlooked a few things. First, not every developer knows each one of the lines in the code well enough as to see where a exception comes from, specially when you are not the author of that code.
Of course, that wouldn't mind so much unless you see another thing, if we don't know which exceptions can be launched by a operation then you will get it on the upper frame and rendered unable to solve it. Use typed exceptions (as this library intends to) you may say. Ok, now we have another problem, the strange habit of coders to keep the exceptions they don't know/can't treat going up and up and up, until then usually hit the top frame and you are screwed. You can check some Java code (to see an example on how this happens) as some of these exceptional conditions are put on the method's signature.
You sound like you are expecting something that works exactly like the imperative exception handling mechanisms you are used to, and are not willing to accept anything else.
No, I sound like I am expecting programmers comming from the imperative world to do ugly things, maybe because I once was one.
This "strange habit" of programmers is simply bad practice, and leads to just as brittle code as no exception handling, except that you get inexplicable error message boxes with OK buttons instead of crashes. You might as well just put the whole program in a catch block in IO and bail with something noncomittal.
Now comes the time when I have to show you that not every exception could be handled, IE a file not found exception when looking for the config file can be fatal and force the program to stop. But what if this is on a library? How do you suggest that the programmer knows that the File Not Found exception is due to the lack of that config file, specially when the code is badly (or not) documented.
IMO, Haskell's typed exceptions (via eg. explicit-exception) *are* the way to go. They keep the number of exceptional conditions that can occur in a body of code small -- if some code has many exceptional conditions, it forces you to *compose* them somehow, to come up with a more precise idea of what your code is doing and how it can fail.
Yeah, the thing comes when you get the same kind of exceptions from different places, you need a way to tell them appart.
If you are just blindly passing exceptions up from the code below, the interface to your code is getting more and more complex. With typed exceptions, the types are reflecting the complexity of the interface (which is precisely the purpose of types).
Well, it's not like blindly pass exceptions up but more like passing exceptions I don't know how to handle up.

klondike schrieb:
Now comes the time when I have to show you that not every exception could be handled, IE a file not found exception when looking for the config file can be fatal and force the program to stop. But what if this is on a library? How do you suggest that the programmer knows that the File Not Found exception is due to the lack of that config file, specially when the code is badly (or not) documented.
A library function that reads a config file may declare to be able to throw the exception "File not found", or it may introduce a new exception "Could not read Config file" with an extra field for the reason, why the file could not be read. This way you can construct a call stack that helps the user (and not the programmer). Then the message reported to the user might be: Program could not be started, because Config file could not be read because Config file does not exist in dir0, dir1, dir2 but the exception handler may also decide to use a default configuration instead or ask the user, how to proceed. Anyway, such a call stack for the user requires different information than a call stack for programmer for debugging.

Henning Thielemann escribió:
A library function that reads a config file may declare to be able to throw the exception "File not found", or it may introduce a new exception "Could not read Config file" with an extra field for the reason, why the file could not be read. This way you can construct a call stack that helps the user (and not the programmer). Then the message reported to the user might be:
Program could not be started, because Config file could not be read because Config file does not exist in dir0, dir1, dir2
but the exception handler may also decide to use a default configuration instead or ask the user, how to proceed.
Anyway, such a call stack for the user requires different information than a call stack for programmer for debugging.
The point here being? My point has been "As programming errors are something common lets at least make them easy to solve". If you make such a fancy stack the programmer is still clueless about where the unhandled exception was generated and can't solve it. There is also another important point here which is error recovery, it's not unusual to see on servers and other high availability programs a last barrier which would show info on the error and then restart/resume the server as if nothing has happened. Following your definition, now we have exceptions, not errors, as they are expected, though they made some harm to our transaction. Henning Thielemann escribió:
The case of (head []) is simple: It is a programming error, since the precondition for calling 'head' is that the argument list is non-empty. The caller of 'head' is responsible to check this. If the list comes from user input, then the program part that receives this list from the user is responsible to check for the empty list before calling 'head'. If there is no such check, this is a programming error, and it will not be possible to handle this (like an exception). Before you answer: "What about web servers?", please read on the article I have written recently to sum up the confusion about errors and exceptions:
Well I got used to going back to the previous state without crashing when I got a precondition violation due to user input. Though I assume that was asking a bit too much of Haskell. Of course crashing the whole program as default behaviour is a better way to solve the problem.

2009/12/07 klondike
Well I got used to going back to the previous state without crashing when I got a precondition violation due to user input. Though I assume that was asking a bit too much of Haskell.
It's too much to ask of partial functions. If you want to state your preconditions and rollback in an appropriate monad, that's of a horse of a different color.
Of course crashing the whole program as default behaviour is a better way to solve the problem.
If you're not working in a side-effecting or sequential subset of the language, you can't really expect to have a consistent notion of the state "before" the crash. It's a declarative, lazy language, after all. Consider, also, that a crash is just one of the problems you get with bad input; another one is infinite loops. As Henning Thielemann points out in his wiki article, you can't expect to catch those as they don't throw exceptions or cause any errors! You need to validate the input or use a timer in that case. Relying on the program to crash in a timely manner if something is wrong with the input is not a strategy that is going to go the distance. -- Jason Dusek
participants (3)
-
Henning Thielemann
-
Jason Dusek
-
klondike