In future, could Kotlin have checked exceptions?

You are right, at first: thats a quite common case, but the API throws SQLException or BadDatabaseExeption (on the open() command) only (both checked).

The specific (implementing) API throws MongoIllegalSelectWhereClauseException which extends the SQLException.

So no need for catching all the specific cases (if you did not like) just the SQLException.

I think the Java JDBC API is designed quite well.

I disagree: “data not exists” is no exception but a (more or less) usual behavior in case of database access, so return of null or an empty list will be the solution.

Here I fully agree.

SUGGESTION:

Language Team, please define lots of exception-Interfaces will help when interpreting exceptions.

The extending SQLException could implement ParseCommandException, ParseValueException.

So a bad “where clause” will throw an SQLException implementing the ParseValueException that could be catched separately.
The toInt(String) function will throw the ParseValueException too.

For future languages (like Kotlin) lots of (well designed) interfaces could make code more readable and somehow more uniform between all programmers. Each interface should be as short as possible (they could be combined later).

Best example is the java interface Iterable.

It depends.

I disagree. An incorrect command indicates a developer error, and developer errors must be unchecked.

BUT, I do not want to discuss the classification of exceptions here. I want to point out that it is very hard to get the Java community and the developer community in general to (mostly) agree on what the best error handling strategy is. Therefore I think it is unlikely you will reach consensus about this for Kotlin, and so I think that a change to the way exceptions are handled will not happen (too much effort for too few developers and/or too little benefit).

I am not against checked exceptions, and I would not mind if they were added to Kotlin. But I would hate to have to struggle with badly designed exceptions of others again.

You are right. That should be some sort of an (unchecked) IllegalArgumentException that could implement ParseCommandException, ParseValueException

1 Like

The problem with checked exceptions comes into place where the exception needs to pass through unrelated code before it can really be handled. This happens in any code with callbacks, and the only way to handle it in Java is using wrappers, or by adding all those types to the exceptions to check (throws Exception anyone?). To me it seems that a Result like monad is the only real solution where you get to check the exception only when you need to get the final value (at which point you’ll have to handle the absence).

1 Like

Indeed, if you convert a contingency to a fault, it is no longer possible to use checked exceptions down the stack. Unless you create something ugly and fragile that unwraps the fault, but I personally would not do this.

I hope the code around the possible exception is not unrelated. Exceptions should be handled as near as possible. Callbacks should not be called after an error, so no need to pass through.
In case of a functional or callback code, there can be additional onError callback or orError function.

No, why running through the complete calling-pipe? Break the functions and simply call an error handler. It is so simple. So, why using ugly optional/error/monad return values just to keep the function-pipe running?

The problem is in things like parser frameworks (or database layers). They will do most of the parsing for you, but then you do your own code to convert the values into the target type. Things like converting a string to an int kind of stuff. This breaks when it isn’t a number, there is little about it that relates to the parser framework (and the parser framework doesn’t know about the user). What happens is that you have an exception that needs to be tunnelled through code that you didn’t write yourself (and cannot change). Wrapping and unwrapping isn’t really a solution in such a case.

The way that works is that you are not actually calling through the pipe. That whole pipe will not be invoked if there is already an exception. What it does is that it allows code to ignore exceptions except where it needs to handle them. At that point they do need to be handled.

Of course Kotlin could do with nicer Monad syntax (like Haskell’s >>=)

Please send a little pseudocode example where and when you give “your” code to the parser framework.

I think you mean function pipes like:
result=a().b().c().d()
So why not breaking in the MIDDLE of the pipe?
try
{ result=a().b().c().d()
}
catch(Exception e)
{ result=error
}

But back to the topic “checked exceptions”

If a() thows ParseException and b() thows NumberTooHighException the complete call expects both (checked!!!) Exceptions.
try
{ result=a().b().c().d()
}
catch(ParseException pe)
{ result=error
}
catch(NumberTooHighException nthe)
{ throw new UnableToAddThatNumberException();
}
So you can propagate the NumberTooHighException upwards (as new UnableToAddThatNumberException) or handle the ParseException directly.

The functions c() and d() will never be executed in these exception cases.

As result this function pipe a().b().c().d() has the signature
fun pipedFunc() throws NumberTooHighException, UnableToAddThatNumberException

So Kolin could provide the generation of such signatures in the same way as the Java compiler stops on “missing catch” semantic errors in code.

The same upward propagation like the suspend keyword
(that should be handled in coroutines)

TOPIC: API-Level functional parameters:

Exception-Specific-API (best case):

If a parse API allows a given function to parse some programmer specific subparts, if should allow this function to throw an (checked !!!) Exception, like ParseExeption and IOException. So that the API can break the parsing at that point and propagate the Exception up to the call of the parser.

So the above mentioned pipedFunction fun pipedFunc() throws NumberTooHighException, UnableToAddThatNumberException
needs to wrap these possible exceptions into a ParseException.

The IOException will not the thown here, but the API could handle it meaningful.

Exception-Tolerant-API:

If the API itself will not make any differences between ParseExeption and IOException it will expect a function f() thows Exception only, so that will be the general case, so no wrapping of exceptions are required then assigning any function to the non specific API.

Back to the root of this Topic:

Should the code handle all checked!!! Exceptions?

In Java the function
bufferedReader.read(InputReader i) thows IOException

Needs to throw an IOException, because the InputReader could be a FileInputReader. I agree, that really sucks If you provide a StringReader you have to handle all the IOExceptions that will never occur.

But the solution of Kotlin throwing away the great concept of checked exceptions is just ducking away from better alternatives:

Suggestion:

@LanguageTeam You think about “declaring throwables” signatures?

bufferedReader.read(InputReader i)

and (function overloading)

bufferedReader.read(InputReader i throws IOException) thows IOException

So the above function accepts the StringReader that will never throw an IOException and the last function will accept the FileReader that will throw IOException

I know, that will not be compatible to Java, but trashing the checked exceptions will not be compatible either.

Checked exceptions are no enemy, but should be seen as normal second way leaving any function. By implementing this “second way” in class/function-signatures they will help building better less error-prone code.

I think you are describing the approach of using runtime exceptions, right? I think topsys was commenting on the approach of not using exceptions, but including the error in the return type of the function.

A fundamental problem (when all exceptions are runtime) of “handling exceptions at the point they need to be handled”, as you state, is that there is no reliable way to know “at the point it needs to be handled” which exceptions can be thrown.

The best thing you can do is rely on the documentation (if available) of the called function, and pray that it lists all exceptions that the caller might want to handle. In practice this will obviously not be the case, which makes this approach very unreliable.

I like this approach very much. As noted before, this meets both well-phrased requirements from Malcolm:

What is not clear to me about the suggested solution, is how it distinguishes between recoverable and unrecoverable conditions. Maybe there were some implicit assumptions with this solution, but I think it is important to make it explicit.

The reason that this distinction is important is that in general, you want to handle recoverable conditions (contingencies) as quickly as possible (close to the call site), whereas unrecoverable conditions (faults) will mostly be handled as late as possible (far from the call site). I don’t see how this solution allows callers to make this distinction.

I can imagine that the Kotlin compiler is able to determine all exceptions that are thrown within a function. These exceptions would then be the inferred exceptions. The exceptions will include both recoverable and unrecoverable ones (e.g. IllegalArgumentException), and it is very hard, or maybe even impossible, for the caller to tell them apart. Kotlin doesn’t have a mechanism to distinguish them.

For this, I can think of one relatively simple solution: Exceptions should somehow indicate if they are a contingency or a fault. The class should carry this information (by implementing some interface, with some annotation, etc.). This should be familiar to Java developers :wink:

Additionally, because inference happens compile-time, it means that it can’t possibly know which exceptions are thrown runtime. If you call a function that is declared by an interface, and you are not aware of the class implementing the function (e.g. because it was injected at runtime), the compiler can’t know which exceptions (recoverable and unrecoverable) are thrown.

For this problem, I can see only one solution: the interface function should (somehow) declare which contingency exceptions can be thrown. Faults could be declared, too, but are less useful.

I realise that this is would be almost identical to Java’s distinction between runtime and checked exception classes, and formal declaration of thrown exceptions (at least for interfaces). But the result is that there is a reliable way to know which contingency-type exceptions are thrown by a function. Obviously it’s still up to the caller to decide to catch them or not.

I am commenting on the approach of USING CHECKED exceptions
(the topic of this thread) as often as possible.

Invisible RuntimeExceptions should be used in cases of computer failures (out of memory, illegal java opcode, …) and programmer errors (divide by zero, illegal argument exception, index out of bounds …)

All other cases should have meaningful checked exceptions!

Even returning -1 is just a hack and did not explain the error.
Is -1 the error, because the values +0 to +n are valid?
Is 0 an error code, because all valid values should be non zero?
Or is 0 an ok code, because -n are error values like in bash?

Hack, hack, hack and totally unreadable after some months.

That would be simple (before Kotlin trashes the concept of checked exceptions):

All RuntimeExceptions are faults and cannot be recovered (perhaps catched by the classloader, a watchdog, an operating system like android to restart the code).

All checked Exceptions are some sorts of expected exceptions like “FileNotFound”, “IOException”, “NoRouteToHostException”.

I don’t think I understand you; didn’t Kotlin already get rid of checked exceptions?

For existing (Java) classes, for interoperability purposes, this is indeed a simple option. I was thinking more of exception classes written in Kotlin today. If such an exception does not extend RuntimeException, you can’t assume it is meant to be a contingency. Unless of course we agree that all all faults extend RuntimeException, as in Java. But this might be an opportunity to choose a better way to identify faults. Personally I think using an annotation is better, because it leaves you the choice to extend any class you’d like.

I understand that. The question was: what would be a reliable way for the compiler to classify these exceptions as contingencies? When written in Java, it can simply be how Java defines checked exceptions (Throwable, except for RuntimeException and Error). But for exceptions written in Kotlin this is not possible, as faults currently don’t have to extend RuntimeException.

Yes, you understand me right, you write “get rid of”.
I mean the same and write “trashing”: so seeing the bad things of checked exceptions only and throwing the whole concept into the trash bin before further thinking (like about the null-pointer problems).

Sadly … you are right.

I think there is no real chance to re-integrate checked exceptions to kotlin.

I think there is. I’ve tried to explain it in my previous post (link), but let me summarise it.

Contingencies
First, I’d like to suggest to not use the term checked exception, but the more abstract contingency. I noticed that using the term checked exception meets a lot of resistance from many people. This is mainly because Java forces you to catch them. But in addition to this property, a checked exception in Java also indicates a contingency, and not a fault. I don’t think there is a lot of resistance to this property. In Java, the two properties always come together, because that’s how the language was designed. In Kotlin, neither exists! We love the fact that we don’t have to catch exceptions anymore, but we’re lacking a way to know the contingencies.

This relatively simple approach fixes that:

  1. Exception classes that are contingencies are annotated with @Contingency
  2. The compiler automatically generates a @Throws annotation on a function for all @Contingency exceptions that are not caught
  3. The compiler (you IDE) clearly informs (warns?) you of all @Contingency exceptions you didn’t catch.

(Instead of annotations, other mechanisms could be used of course, but the idea remains the same)
Clearly, this is exactly the same approach as in Java, except that you’re not forced to catch any exceptions.

Java interoperability
All exception classes that are considered to be a checked exception in Java will be inferred as @Contingency exceptions by Kotlin.

Kotlin backward compatibility
The same rule applies to exception classes written in Kotlin of course. The only problematic situation is that if an exception is written in Kotlin, is not annotated with @Contingency, extends e.g. Exception directly, and is meant to be a fault (not a contingency), it will be incorrectly classified as contingency. Your IDE will warn you if you don’t catch it, which is annoying, but an acceptable corner case in my view.

1 Like

First, I love the fact that checked exceptions are missing.
I do use the functional way, though: the Try-class.

The Try-class with getValueUnsafe is the same as unchecked exceptions.
If you don’t have that function, you do need to check the type → checked exception.

Try-classes with all their callbacks are harder to chain.
But I believe something like that can be solved the same way suspend already solves multithreading.

try fun foo() : String = ""
fun outside() = when(val v = foo()){
    is Success -> println(v.value)
    is Failure -> println(v.failure.message)
}

try fun inside(){
     val value = try { foo() } catch(e : Exception) { it.message }
     println(value)
}

or if you want to simple rethrow on exception:

try fun inside() = println(foo())

Here you tell people clearly that you expect some exception to be thrown.
The drawback:

  • Silencing can easily be done by adding tthe getValue or adding try before the function.
    This way, either lots of functions would become try-functions, or a lot of functions are silenced.
  • Also, it doesn’t tell which exceptions can be thrown.

If a (way better) function like this would be implemented, it would be done in arrow-kt, combined with arrow-meta.