Hacker News new | ask | show | jobs
by vbezhenar 391 days ago
The general issue, as I see it:

One function is too general and handles all inputs. Some inputs are wrong and code returns error. Some languages forces you to handle that error.

Another code snippet uses that function with very specific inputs which must not cause errors, but was forced to handle error, because of language rules. This error handling is essentially useless.

I saw this issue with Java. There's constructor `new URI(String) throws URISyntaxException`. URISyntaxException is checked exception which must be handled at invocation site or propagated to the caller. But you might need to write code like `var uri = new URI("https://example.com/")` and any error handling code will only be to please the compiler, essentially wasted work. Java solved it using `public static URI create(String str)` which does not throw checked exceptions and you're supposed to choose either constructor or factory method, depending in the circumstances.

Using Rust analogy, may be it would be useful for Java to write something like `var uri = unchecked new URI("https://example.com/");`, so with minimal syntax overhead you would signal that checked exceptions are not supposed to occur here (and if they did occur, then some unchecked UnexpectedCheckedException would throw here). It's actually possible to implement method `static T unchecked(CheckedSupplier<T, E> supplier)`, though syntax would be ugly: `var uri = unchecked(() -> new URI("https://example.com/"))`.

I think that this is general issue for programming languages. There's only one way to validate input parameters and report error. But whether those input parameters are trusted or not - only the caller knows. So nothing wrong about using `unwrap()` to signify the fact, that caller already made sure that error does not happen. The only important nuance is that error must not be swallowed if it happened.

2 comments

It's a general issue with typed languages. Types are either too fine-grained or too coarse. Some more sophisticated languages can infer a whole lot but static type checking is still confusing and slows down builds. I tend to not worry much about them anymore -- about C language level types (maybe a bit more) is helpful and important for performance, but beyond that (e.g. types get dynamically instanciated from templates by the compiler) returns are diminishing quickly for many tasks.

The biggest robustness gains often come from making the right architectural decisions. That also applies to error handling as you described.

After starting my career in dynamically-typed systems for about the first 15 years, I'm pretty solidly in the camp of statically-typed systems.

However, I think it's really easy for a statically-typed-systems user to get a bit too enamored with static types even so, and start trying to stuff too much stuff in there. Anyone who thinks that the job of static types is to make all conceivable errors impossible is invited to go learn Haskell and then spend some time trying to write a non-trivial program, like, say, a GUI that also interacts with the network somehow, using an effects system to its maximum capability to perfectly carefully constrain every single function.

It is academically impure to do things like the author mentions and have a search object that will fail if you call a certain method without having constructed it a certain way. No question. And no question, this impurity can result in bugs in real code in the real world.

On the other hand, if one takes the time to create the absolutely perfect crate for Rust that absolutely perfectly expresses all possible combinations of options and methods that such a package could have, you could conceivably end up with a package with literally 10 times or more the number of types, that requires more steps to create a search than the simpler one, that is in fact so complicated that it turns the simple task of "please look for string x in string y" into something that a new programmer can't even understand anymore, and even a senior programmer might have to fight through some docs and ultimately just copy/paste an example and hope for the best...

... and the designer must ask the question, is that actually better? In practice, and not in theory?

Bugs are not created equal.

The academic worldview implicitly accepts the premise that an infinite amount of work is worth doing to eliminate the smallest possibility of the smallest bug with the smallest consequences.

A coder who lives in the real world needs to examine that premise carefully and decide if it matches their current situation, and if as is quite likely it does not, take appropriate actions. Static type systems offer a fantastic cost/benefit tradeoff in many real-world situations, but there is a point of diminishing returns.

I think the "correct" way to do it at a language level is for the compiler to construct the URI at compile time when possible. The successful construction proves that it cannot throw exceptions and hence they don't need to be handled.