I'm building a new language, somewhat similar to TypeScript in some ways, and so far I have exceptions and try/catch expressions, but also Optional<T> and Result<T, E> types.
I'm familiar and used to exceptions, so I included them so at least near-fatal errors (ie, actually exceptional) could be caught at high levels in the stack. But I'm unsure if there's a strong argument that resonates with me yet that the language shouldn't have exceptions at all. Arguments that exceptions are untyped can be solved with things like checked exceptions, and I do find Go-style code to be quite verbose.
What's the best current reading on this?
Which leads me to: why is Kotlin implementing this in a non-JVM compatible way, instead of introducing checked exceptions with better language support? All the problems stated above can be avoided while keeping the core idea of checked exceptions, which seems to be the same as this proposal.
From the GitHub discussion, I see this comment (https://github.com/Kotlin/KEEP/discussions/447#discussioncom...):
> The difference between checked exceptions from java and error unions in this proposal is how they are treated. Checked exceptions are exceptions and always interrupt the flow of execution. On the other hand, errors in this proposal are values and can be passed around as values or intentionally ignored or even aggregated enabling the ability to use them in async and awaitAll etc.
But is this a real difference or something that can be emulated mostly syntactically and no deeper than Kotlin's other features (e.g. nullables, getters and setters)? Checked exceptions are also values, and errors can be caught (then ignored or aggregated) but usually interrupt the flow of execution and get propagated like exceptions.
The one feature that the proposed Kotlin error types share with Java checked exceptions is that they can be collected in unions. However, the union feature for checked exceptions is pretty much useless without the ability to define higher order functions that are generic over such unions, which is why checked exceptions fell out of favor with the spread of functional APIs in Java 8.
Checked exceptions only make sense for errors that are relatively common (i.e., they aren't really exceptional), which calls for a different implementation entirely where both the happy path and the error path have around the same cost.
This is what modern languages like Rust and Go do as well (and I think Swift as well though don't quote me on that) where only actually exceptional situations (like accessing an array out of bounds) trigger stack unwinding. Rust and Go call these panics but they are implemented like exceptions.
Other errors are just values. They have no special treatment besides syntax sugar. They are a return value like any other and have the same cost. As they aren't exceptional (you need to check them for a reason), it makes no sense to use the exception handling mechanism for them which has massively skewed costs.
I don't know about Rust, but a very important difference between Java exceptions and Go panics, is that a Go panic kills the entirely process (unless recovered), whereas a Java exception only terminates the thread (unless caught).
It's a little off-topic, but I wanted to clarify that for passer-bys who might not know.
Depends on the software, huh.
Having worked extensively with Node.js (callback hell, then Promises), I appreciate how error-as-value patterns force you to think about failure cases at every step. But the reality is most developers don't - they either:
1. Ignore the error case entirely (leading to silent failures) 2. Bubble everything up with generic error handling 3. Write defensive code that becomes unreadable
Rust's Result<T, E> with the ? operator found a sweet spot - you have to acknowledge errors exist, but the syntax doesn't make it painful. The key innovation is making the happy path concise while forcing acknowledgment of errors.
For Kotlin specifically, I'm curious how this interops with existing Java libraries that throw exceptions. That's always the challenge with these proposals - they work great in greenfield code but break down at library boundaries.
The real question: does this make developers write better error handling code, or just more verbose code? I'm cautiously optimistic.
One thing I notice in enterprise java software that I have to reed through and update, is that too many times, every developer just wraps everything in an exception. I do not have vast insight into all java code, everywhere, but in my little corner of the world, it sure looks like laziness when I have to dig through some ancient java code base.
There would be real value in a language which would have both.
Error values are perfect for un-exceptional errors, e.g. some states of a business logic. The name that the user entered is invalid, some record is missing from the database, the user's country is not supported. Cases that are part of the business domain and that _must_ be handled, and therefore explicitly modeled.
Then there is the grey area of errors that one might expect (so not truly exceptional) but are not related to the business logic. These could be for example network timeouts, unexpected HTTP errors (like 503), etc. For those, there is often no explicit handling in the domain that makes sense. So it's convenient to just throw an exception, let it automatically "bubble" to the highest level (e.g. the HTTP controller) and just return some generic error (such as HTTP 500).
There are also truly exceptional cases, that you really shouldn't encounter in your program, such as null-dereferences, invalid array index access, division by zero, etc. These indicate a bug in the code (and might be introduced explicitly with assert-style checks). The program is in an unknown, compromised state, so there's really nothing left to do than throw an exception or panic. An error value makes very little sense in this case.
I often have the discussion with friends, why a division operator, or an array access, doesn't return a `Result` type in nice languages such as Rust? Surely, if they care about error values, then each operation that can fail, must return a `Result` rather than panic (throw an exception). It is an interesting through experiment at least.
I mean, this could be a syntax wrapper for java checked exceptions right?
Those are isomorphic to Result<_, Err> in that you must handle or propagate the error. The syntax is different, sure.
If there were no checked exceptions to begin with, people might have thought about making the Java compiler (and later language server) infer all possible exception types in a method for us (as effect systems do). One could then have static analysis tools checking that only certain exception types escape a method, giving back checked exceptions without the type and syntax level bifurcation.
On the other hand, if all exceptions were checked, they would inevitably have had to implement generic checked exception types, ironically leading to the same outcome.
I think it's good nonetheless to add stuff to Kotlin that won't translate 1:1 to Java, both because Java is evolving but also because Kotlin is used in "Native" (non-JVM) contexts as well (not extensively, but hopefully that'll change).
C++ cannot get away from C, Typescript cannot get away from JavaScript, and so forth.
Background: unions aren't restricted to one normal type and one error type, but to one normal type and any number of error types, so this can't be modelled as syntactic sugar on top of an implicit Either/Result type, can it??
The anti-Java bias, against the platform that made it possible in first place and got JetBrains a business, is quite strong on Android, fostered by the team own attitude usually using legacy Java samples vs Kotlin.
From the proposal discussion[0], the runtime representation on the JVM will just be `Object`.
[0]: https://github.com/Kotlin/KEEP/discussions/447#discussioncom...