Reviving Checked exceptions

Great — then we’ll have two different, conflicting ways of handling errors that everyone will have to learn. And every time we have an error situation, we’ll have to decide which way to raise it. And we’ll have to check for both types… :confounded:

Having thrown the baby out with the bathwater by destroying checked exceptions, it looks like they’re now trying to recreate them — badly.

Kotlin is what it is. It’s not Haskell or some other pure FP language. It already has (the remains of) an error-handling mechanism — one that’s not perfect, but could be improved (see above). So why not try to fix that, instead of bolting on some new mechanism that’s disruptive and awkward, not to mention incompatible with 12 years’ worth of existing Kotlin code and 29 years’ worth of existing Java libraries?

This is IMHO the only valuable part of it. But that’s effectively what checked exceptions are in Java!

And as I said above, exception inference would bring back most of the benefits of that to Kotlin (but without the boilerplate, and without losing compatibility).

1 Like

So why not try to fix that, instead of bolting on some new mechanism that’s disruptive and awkward

Exceptions have produced some awkward artifacts in Kotlin stdlib: The duplication of a number of functions in two distinct flavors : one that throws an exception, and one that returns null (first vs firstOrNull, toInt vs toIntOrNull, etc.). Union type with error can solve this problem :

fun List<T>.first() : T | NoSuchElement {
    return if isEmpty() NoSuchElement() else get(0)
}

// Throw an exception from returned error, if any
val firstValue = myList.first()!!

// Or ignore error and get a default value instead
val firstValue = myList.first() !: null

I mean, in term of ease of use and expressiveness, I do not see how we could even get close to this with exceptions.

For me it makes sense to get a lightweight and simple error system for common errors (union type for error), and keep runtime exception for uncommon errors that might be difficult to recover.

So why not try to fix that, instead of bolting on some new mechanism

Because it is unfixable ? Even if you get checked Exception, they still are extremely heavy objects (because they build a stacktrace when they are created), and they do not fit well in the type system (otherwise, java lambdas and streams would be able to work with them, which is not the case, and I see no plan for improvement in this regard in any jep).

The new “union type for errors” might solve many problems in the same time :

  • Providing a more ergonomic and powerful alternative to Result and Either
  • Unifying error nullification and exception on error apis (first() vs firstOrNull() becomes just first()!! or first() !: null).
  • Introduce pattern matching capability for error management (using when (result) { is Error1 → … })
2 Likes

Hi @alexis.manin
can you propose a more complex examples to explain how this idea definitively fixes well-known issues of checked exceptions.

I already reported my concern:

I have a list of lists of integers: val lists: List<List>
Kotlin standard lib provides me the definitive maxOf method: fun <T, R: Comparable<R>> List<T>.maxOf(selector: (T) -> R): R | NotFound

I need to find recursively the maximum value in lists: res = list.maxOf { l -> l.maxOf { it } }

What is the expected behaviour after this KEEP and how to fix my code in case of list = listOf<List<Int>>(emptyList())?
Is it NotFound?

Probably Kotlin standard lib should be: fun <T, R: Comparable<R>> List<T>.maxOf(selector: (T) -> R | NotFound): R | NotFound ?

Here is a problem with checked exception: you have to know all checked exceptions in advance, because they are part of the signature.
For instance, if I get a GetError during comparison (see this comment), should I return NotFound?
Otherwise, my method signature would need to be: fun <T, R: Comparable<R>> List<T>.maxOf(selector: (T) -> R | NotFound | GetError): R | NotFound | GetError

In this topic and in KT-68296 Union Types for Errors, many people have highlighted the significant enhancements this proposal could bring, suggesting it combines the best of both worlds (no exceptions and checked exceptions).
From my perspective, however, it resembles Java’s checked exceptions, an unsuccessful choice that could result in the worst of both worlds. The result could be of type R, a checked failure, or an unchecked exception, and every developer would need to handle all of them.

I hope that in the analysis of KT-68296, an enthusiast can explain how this feature definitively addresses all the well-known issues associated with checked exceptions.

Error nesting is one of the biggest pain point, you’re right.
They’re also one of the biggest challenge of this proposal, I think.

You’re right that checked exception have made this complicated (this is why they’re not compatible with lambdas).

Now, for utfe (Union Types For Errors), I think that the most pragmatic solution is to apply the same compromises as with CheckedExceptions. But, the major difference is that the error type is fully included in the result type, making it easier to code. Let’s compare some of the possible solutions:

  1. “Uncheck” nested errors : The maxOf function could consider that errors raised by the selector are not its responsibility, and you will have to transform them into runtime exception: myList.maxOf { l -> l.maxOf { it }!! }.
  2. You have to propagate the error type in the result. In this case, the Kotlin stdlib function could have the following signature:
/**
 * @param T: input list element Type
 * @Param R: comparable elements Returned by the selector
 * @Param E: Error types (possibly union type) possibly raised by the selector
 */
fun <T, R: Comparable<R>, E> List<T>.maxOf(selector: (T) -> R | E): R | E | NotFound {
    var result : R | E | NotFound = NotFound()
    for (item in this) {
        var value = selector(item)
        value = value !: return value
        result = result!.let { max(it, value) } !: value
    }
    
    return result
}

In this case, you use it just like this: myList.maxOf { l -> l.maxOf { it } }, and you will get back a NotFound result.

@alexis.manin thank you for reply.

I vote inheritance, but I agree that composition is a good candidate.

I consider these compromises enough to elect checked exceptions as bad design, IMHO.

I am really interested to this part, really.
I hope this bring a syntax enhancement to Kotlin without putting checked exception into the party.

  1. Already known as “sneaky throws” to disable checked exceptions.
  2. <T, R extends Comparable<T>, E extends Exception> R maxOf(...) throws E, NotFound, not so many fan in Java. Furthermore, this choice force to handle E always and limits all exceptions under the same super-exception, often Exception in Java (because Exception disables checked exception).

I am sorry, but I consider that way is unsuccessful.
I hope you will fix this issues.

In Java, a method that is declared to return Optional can also return null. I don’t see how you think that having Optional saves you from null checks. Similarly, a method that is declared to throw no checked exceptions can still throw RuntimeExceptions and Errors. I don’t see how you think that having checked exceptions saves you from unexpected throws.

1 Like

I thought we were on Kotlin forum :slightly_smiling_face: and in Kotlin, when a function returns Object or Object? definitely helps you prevent the unnecessary nullability checks.
Maybe optionals was misunderstood as type “Optional” and not as nullable types.