Live data from Hacker News

You’re better off using Exceptions

eiriktsarpalis.wordpress.com

181–190 of 242 posts

Re: You’re better off using Exceptions

#181
The real problems with exceptions are that they have a large performance overhead, and that they differentiate poorly between anticipated errors and truly exceptional situations.

The performance overhead comes from grabbing a stack trace and constructing an object that lives on the heap, and then unwinding the stack. (Some assembly experts can probably explain this overhead better than I can.). When your code handles an error condition that it anticipates, that overhead is wasted CPU cycles.

(This is why Exceptions aren't for flow control.)

What we really need are languages that differentiate better among success / error / exception. Go has panic / resume, and Java has compiler-enforced exception handling, unless it's a runtime exception.

What I want instead is something where I have optional and convenient syntax to handle lightweight errors; or the ability to automatically convert errors to exceptions if I don't handle them.

TLDR:

Think of the query to lookup a record by ID case. If the record isn't found, it's an error that doesn't have a stacktrace or an object on the heap. But, if my code doesn't handle the error, then it's a full exception with a stacktrace.

Re: You’re better off using Exceptions

#182

Earlier quoted context omitted.

Thats not how f# works normally. Normally you chain option operations with either binds, or you do a match(Which is exhaustive). Directly accessing the value is normally frowned upon. This guy either needed to make the option type go away, or filter out the None and pass that into average. List.choose could be used for this let average (customers : Customer list) = match customers with | [] -> Error "list was empty"…

In support of the article, your code would still throw an exception if all the customers in the list have missing credit. https://github.com/dotnet/fsharp/blob/master/src/fsharp/FSha... You could instead write it as the following to handle that. let average (customers : Customer list) = customers |> List.choose (fun c -> c.Credit) |> function | [] -> Error "no customers with credit" | xs -> Ok (List.average xs)

Indeed. I was remembering the behaviour of average off the top of my head.

Re: You’re better off using Exceptions

#183

Earlier quoted context omitted.

C++ too. I suspect it’s true in almost every language. Maybe not Python? But Python is slow regardless.

In C++, exceptions are often faster than manual error checking when errors are rare. But C++ does not provide a stack trace.

C++ exceptions are only good if you use them like signals from C or panics in Go. This means you have to use error values for 99% of errors or you lose this benefit. The fact that the STL sprinkles exceptions everywhere doesn't help this. C++ exceptions are so non-deterministic and slow that every real-time system disables them just to be sure.

Re: You’re better off using Exceptions

#184

Earlier quoted context omitted.

That is like C++ destructors, which are called deterministically when they go out of scope (again with no "try" syntax). They can unlock mutexes, close files, etc. But unlike what you describe, there is no need to put anything at the end of the function: instead, the fact that you have instantiated an object already guarantees that its destructor will be called later. This is called RAII, short for "resource acquisit…

Yes... RAII is handy, but it doesn’t save you from checking EOF logic in my example.

You could use a File class that throws on EOF, but that would be a really bad idea for C++ since throw/catch are very expensive.

Re: You’re better off using Exceptions

#185
post #170
post #59

> It’s by such misadventure that the working F# programmer soon realizes that any function could still potentially throw. That's your problem right there. "Invisible control flow", as Joe Duffy puts it. http://joeduffyblog.com/2016/02/07/the-error-model/#unchecke... What's needed are two distinct error mechanisms: panic/abandonment for unrecoverable errors (which can happen at any time), and then for recoverable erro…

But then the failed code which decides if the error is recoverable, not the part of the code doing the recovering.

Yes!

We shouldn't expect downstream to wrap every array access which could be out of bounds in a "try" block. We shouldn't expect downstream to wrap every division operation in order to catch potential divide by zero errors. Those should be fatal errors, catchable only at a very coarse-grained level, because once they occur the program state is potentially compromised in a way which the programmer did not plan for.

The library author gets to decide whether a potential failure mode should expose a recovery API.

Re: You’re better off using Exceptions

#186

Earlier quoted context omitted.

I would compare this to a map API: an access could throw if a key wasn't present, but you can check to see if the key was in there first anyways. As long as the map isn't being used concurrently, it isn't that much of a problem, and if it is being used concurrently, then you need a different design. In the former case, separating "exists" and "get" would be considered over-engineering. I guess databases and file syst…

For most implementations of maps (hashtables and various tree-ish things), even in the single-threaded case, doing .exists and then .get is about twice as expensive as doing a .get that returns an Option (or nullable reference, default value, whatever)- you have to do the map lookup twice. (Okay, probably not twice as slow, since the cache will be hot, but still.)

Not twice as slow. But you are forgetting the case where you know the element is already in the map, and you want to call map.get() without wrapping it in an option.

Re: You’re better off using Exceptions

#187
post #62

Earlier quoted context omitted.

> A record not being found is a normal thing! It's not a normal thing for code that needs that record that wasn't found. > They literally tell you nothing and there's no way to solve them without catching/rescuing them. A null value, a plain "error" object, or an error argument in a callback would have been sufficient. If I need an exception to be raised for this kind of thing, I'll do it myself. They tell you lots:…

> It's not a normal thing for code that needs that record that wasn't found. No offense, but I don't know where that idea comes from. Systems are checking for records all the time in ways where the absence of data doesn't necessitate throwing an exception. For instance, a page, user, or piece of media on a website may have existed at one point but was since deleted, but still has a permalink floating around the net.…

That's a pretty good example. Now imagine you want to structure your code in a way that render('record-not-found') happens in a caller to that block. So exceptions as a general approach work pretty well to allow simple code organisation according to your needs.

And you are the one who knows best if something is an expected failure, so wrap that up in a fetch_one_or_none() — as a matter of fact, most DB interfaces I work with have an API of the kind.

Basically, exceptions are a decent way to keep what might need to be a separate control flow, well, separate. And you can choose at any time to "join them in". As a concept, they are quite like interrupts on the very low (hardware) level, so you can learn only one "tool" to deal with them.

(And yes, I can trivially find cases where I want errors to bubble up when a record is not found, eg. a required configuration record or similar)

Re: You’re better off using Exceptions

#188

Exception != Error Old Ada programmer here. Example of reading bytes from a file... just keep reading bytes and don’t include logic for checking for EOF. Let the exception handler catch it where the file will be closed. Clean separation of code. In Ada, every bock can have exception handlers at the bottom. No need for “try” syntax. Very clean.

> don’t include logic for checking for EOF. Let the exception handler catch it where the file will be closed

What happens if the file is smaller than you anticipate?

That's why exceptions aren't used for flow control.

Re: You’re better off using Exceptions

#189
post #19

Earlier quoted context omitted.

I don't know Rust at all but what you're talking about sounds equivalent to (much maligned) Java's checked exceptions system? I never understood the hatred checked exceptions received especially from the younger crowd. I still write Java at work and I still use checked exceptions whenever they indicate an error condition that must not be ignored by the client code. Many new to the project developers hate me for it in…

I haven't written very much Java, but here are some differences between Java checked exceptions and Rust errors as I understand them: In Java, a function may throw a long list of checked exceptions, and these lists tend to grow to inconvenient sizes in larger programs. For example, if foo() calls bar() and baz(), which each throw 3 different exception types, then now foo() might throw 6 different exception types. In…

You can also choose to stop playing the game and wrap any checked Exception in an unchecked RuntimeException. Or, if you use Lombok, you can apply the @SneakyThrows annotation to quiet the compiler. As the idea that checked exceptions were a design mistake gains mindshare, you see these tricks more and more in newer codebases.

Re: You’re better off using Exceptions

#190

Earlier quoted context omitted.

C++ too. I suspect it’s true in almost every language. Maybe not Python? But Python is slow regardless.

In C++, exceptions are often faster than manual error checking when errors are rare. But C++ does not provide a stack trace.

You don’t always get a stack trace, but it still has to unwind the stack, calling destructors as needed, and check the exception’s type against each catch block.
Post reply on HN