Live data from Hacker News

Errors and Exceptions

giantfublog.wordpress.com

1–10 of 55 posts

Re: Errors and Exceptions

#2
Linux wants to be a 1970's mainframe.

TempleOS wants to be a modern C64.

In 1970's they had 100 users on 2 Meg of RAM.

Today, nobody would tolerate how slow disk swapping is, if you ever actually rand out of RAM and started swapping.

Re: Errors and Exceptions

#3
Well, that's one position on the subject. The Rust people prefer option 3 over option 4. Go takes the same approach. Python prefers exceptions, and the exception hierarchy puts (almost) all the exceptions which result from external problems under EnvironmentError.

Much of the trouble with error returns comes from the strange C convention that functions with return values can be called as if they didn't return a value. This is a consequence of the original K&R PDP-11 implementation without function prototypes, and has messed up programmers for four decades now.

RAII (resource acquisition is initialization) is popular in C++, but if anything goes wrong in a destructor, things usually come unglued. I/O errors on file close where close is via a destructor tend to be either ignored or fatal. Python "with" clauses handle failure during release better. This was well thought out in Python; exceptions in "with" clause exits are handled rationally.

With Javascript, you get none of the above, although you can build it out of the primitives, and there are kludges to do this.

Re: Errors and Exceptions

#4
He talks about "soft" errors as if the only way to handle them is to propagate them up to the UI layer, displaying a message.

In many situations the error handling affects the code paths at a more profound level. Something might be considered an error in the callee, but at the same time it might be an acceptable result in the caller, for which a well-defined path exists. Throwing an exception in such situations is just disconnecting the faulting code from the error handling code.

Re: Errors and Exceptions

#5
post #3

Well, that's one position on the subject. The Rust people prefer option 3 over option 4. Go takes the same approach. Python prefers exceptions, and the exception hierarchy puts (almost) all the exceptions which result from external problems under EnvironmentError. Much of the trouble with error returns comes from the strange C convention that functions with return values can be called as if they didn't return a value…

I also prefer option 3 over option 4. It bothers me that the article recommends exceptions without seeming to understand their drawbacks (the major one: as soon as you use exceptions you suddenly need to apply nonlocal reasoning everywhere in order to understand what your program will do at any point. They turn your program from a simple local thing into a complex nonlocal thing, which is not a good idea if you want to understand it well.)

This article seems not to have a lot in the way of new contribution; it is just parroting the oft-repeated idea that you need exceptions to pass "error information" up several layers of abstraction. But here is what I think about this:

1) In this age when we are realizing strong typing is a good idea, that hidden state is a bad idea, and that in general you should be very specific about what is going on, why are we even conceptualizing this as "error information"? Why instead, when we try to open a file, do we not return "all information you might need to know in the case of opening a file" (which includes what happened if it didn't open properly). As soon as you make that conceptual switch, all this hand-wringing goes away. It's a non-problem. You certainly shouldn't add heinous complications to your program to solve this non-problem.

1a) This conceptual change also helps disambiguate between what the article calls "hard errors" and "soft errors". In portions of the code where you have attempted an operation that might have failed, and you are not completely sure that it didn't fail, you have the full body of "what happened" information (it is a small struct or whatever). After the situation has been checked and you know it is exactly what you need to be, you may drop the other information and pass the raw file handle. At this point it is clear that these parts of the code should only be executed if the file handle is valid, and if that is not true, the programmer made an error. This is analogous to the situation with nullable and non-nullable pointers (in some languages you would even use the same mechanisms to deal with null pointers and invalid file handles, etc, but I am not sure this is really helpful.)

2) If one insists on not making the simplifying leap from (1), well, maybe the other problem is that you have so many layers. If you didn't have so much glue, your code would be simpler and easier to deal with, and it would run faster, and you wouldn't be so worried about needing to pass lots of context information up several layers between modules, because those situations don't really arise.

Re: Errors and Exceptions

#6
post #3

Well, that's one position on the subject. The Rust people prefer option 3 over option 4. Go takes the same approach. Python prefers exceptions, and the exception hierarchy puts (almost) all the exceptions which result from external problems under EnvironmentError. Much of the trouble with error returns comes from the strange C convention that functions with return values can be called as if they didn't return a value…

Go's preference for 3 over 4 is a big chunk of the reason I've never used it in anger. Rust's situation is slightly different; code typically uses a monad (Result) for error propagation, which is mostly isomorphic to exception throwing, but more verbose. And Rust has macros to cope with some of the verbosity.

Re: Errors and Exceptions

#7
post #5
post #3

Well, that's one position on the subject. The Rust people prefer option 3 over option 4. Go takes the same approach. Python prefers exceptions, and the exception hierarchy puts (almost) all the exceptions which result from external problems under EnvironmentError. Much of the trouble with error returns comes from the strange C convention that functions with return values can be called as if they didn't return a value…

I also prefer option 3 over option 4. It bothers me that the article recommends exceptions without seeming to understand their drawbacks (the major one: as soon as you use exceptions you suddenly need to apply nonlocal reasoning everywhere in order to understand what your program will do at any point. They turn your program from a simple local thing into a complex nonlocal thing, which is not a good idea if you want…

In this age when we are realizing strong typing is a good idea, that hidden state is a bad idea, and that in general you should be very specific about what is going on

Java tried to be typesafe about errors, about not obscuring the state, and it was a dreadful idea. The way code fails is a function of its implementation; communicating high quality error information is fundamentally an abstraction violation, but a strongly typed facade that forces you to rewrap all your error cases in some other type just obscures the underlying problem.

Strongly typed error information also breaks composability. How do you write a 'map' function that can accept a callback, where the callback may want to communicate back one of several distinct kinds of error information? With unchecked exceptions, the way forward is clear. Without them, you either need to write a lot of boilerplate to unify your error types in some kind of container, or rely on subtyping to fit them into a single type and live with the lack of strong typing via another route.

Dynamically typed programming languages improve usability over statically typed languages particularly when a precise description of the types used in the static case is difficult or tedious to express. Error information, and its intrinsic implementation-dependent content, is one of the strongest instances of this.

Re: Errors and Exceptions

#8
post #3

Well, that's one position on the subject. The Rust people prefer option 3 over option 4. Go takes the same approach. Python prefers exceptions, and the exception hierarchy puts (almost) all the exceptions which result from external problems under EnvironmentError. Much of the trouble with error returns comes from the strange C convention that functions with return values can be called as if they didn't return a value…

> Much of the trouble with error returns comes from the strange C convention that functions with return values can be called as if they didn't return a value.

The "warn_unused_result" GCC function attribute makes the compiler emit a warning when you do `func(something);`: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attribute...

Re: Errors and Exceptions

#9
post #3

Well, that's one position on the subject. The Rust people prefer option 3 over option 4. Go takes the same approach. Python prefers exceptions, and the exception hierarchy puts (almost) all the exceptions which result from external problems under EnvironmentError. Much of the trouble with error returns comes from the strange C convention that functions with return values can be called as if they didn't return a value…

Most statically typed languages prefer 3 over 4. For the simple reason that it's easier for a compiler to verify that you've handled all the success and error cases.

For Haskell though there exists a library which allows the compiler to track exceptions in much the same way as normal values, and thus warn you when you fail to handle an exception in a particular code path.

Re: Errors and Exceptions

#10
post #7
post #5

Earlier quoted context omitted.

I also prefer option 3 over option 4. It bothers me that the article recommends exceptions without seeming to understand their drawbacks (the major one: as soon as you use exceptions you suddenly need to apply nonlocal reasoning everywhere in order to understand what your program will do at any point. They turn your program from a simple local thing into a complex nonlocal thing, which is not a good idea if you want…

In this age when we are realizing strong typing is a good idea, that hidden state is a bad idea, and that in general you should be very specific about what is going on Java tried to be typesafe about errors, about not obscuring the state, and it was a dreadful idea. The way code fails is a function of its implementation; communicating high quality error information is fundamentally an abstraction violation, but a str…

You could have checked exceptions with more flexibility than Java had, and it might work out well (or might break down for other reasons).

"How do you write a 'map' function that can accept a callback, where the callback may want to communicate back one of several distinct kinds of error?"

With Java style checked exceptions, you don't, and that's for sure a problem.

However, one approach would be to say that map is polymorphic in the exceptions that may be raised from within it. But that set is not entirely unbounded - it is precisely the set that can be raised by its function argument, unioned with any that might be raised during traversal of its container argument.

Post reply on HN