State of Kotlin.Result vs. kotlin-result?

It is my understanding that even in the latest Kotlin version (1.4.31), Kotlin.Result is disabled by default as a return type, and must be explicitely enabled to be usable as one by passing the -Xallow-result-return-type compiler argument. But then there’s the kotlin-result module, which also adds a Result class.

What is the relation of kotlin-result with Kotlin.Result? Are there any plans on fixing the underlying Java interop issue that causes the Kotlin.Result return type problems? And is kotlin-result also affected by this?

I ask because I am currently choosing whether to use kotlin-result (which means importing an entire module just for using Result and fold) or pass that compiler argument (= no extra module, but enables an unstable feature).

3 Likes

I’ve had a quick look at kotlin-result, it adds quite some elements. The implementation is also quite opinionated and probably not suitable as standard library. It uses a sealed class as basis where the kotlin Result type uses a clever inline class where normal results work as expected and errors are wrapped in an invisible Failure container (which is used to determine failure - while allowing all other types to be safely used as result value - and the container itself is not user obtainable).

In design, I would probably go with the Kotlin result type, but note that this was basically introduced to help implementing coroutines. It wasn’t designed as a stable API (also when inline classes weren’t stable), even though it seems to be a sensible api.

For the lazy ones who wonder, kotlin-result is at GitHub - michaelbull/kotlin-result: A multiplatform Result monad for modelling success or failure operations.

1 Like

TL;DR: kotlin.Result is half-baked and is likely to change in the future.

A little bit of history - kotlin.Result was introduced in 1.3 as an internal class first, as it was intended to be used by compiler generated code only - namely coroutines. However, it was during testing process made public, since programmers saw the usefulness of the class as a Try monad and some of our users, particularly, arrow project, wanted to replace their own implementations of Try monad with the standard one.

Despite being forbidden by default, return kotlin.Result from a function is fully supported in the compiler and we try not to break the code, which returns kotlin.Result.

The reasons why it is forbidden by default are two-fold.

First, we might want to treat kotlin.Result similar to nullable types. I.e. possibly add a new syntax, for instance, ceylonesque T | IllegalStateException. Additionally, we might want to extend ?. and ?: to support that kind of types. Similar to nullables, ?. is to retrieve value, and part after ?: is executed if there is an exception, wrapped in the result.

Second, Kotlin does not support checked exceptions of any kind. However, some exceptions, like FileNotFoundException are checked in Java for a reason, and not catching the exception is an error. Returning result class enforces a user to check the value. So, it can be viewed as a replacement for checked exception. In that case, it is logical to compile functions, returning kotlin.Result as functions, returning value type and throwing the exception, so, when the user calls the function from Java, it is seen as a function, throwing checked exception, and thus, the user has to catch it.

Because of these two design issues, we do not encourage use of kotlin.Result as a return type - because sometime in the future there can be a breaking change of some kind. However, since we see the need for checked exceptions and Try monad, we do support it.

5 Likes

Change of plans. We will allow returning kotliln.Result from functions. For checked exceptions there will be another feature, which does not involve kotlin.Result. Reason is simple: kotlin.Result does not allow specific exceptions, which is needed for checked exceptions. It is not yet clear, how checked exceptions will look like. The best candidate is contextual receivers.

7 Likes

ooooooooooooh receivers! Is that related to the interal discussion about coeffects by any change

Only guessing here but perhaps contextual receivers is related to the upcoming decorators feature and the potential ability for them to specify multiple receivers.

Consider this code:

sealed class DangerZone
private object PiranhaPool : DangerZone() //Impossible create DangerZone elsewhere
fun letsGetDangerous(block: DangerZone.() -> Unit) = try {
    PiranhaPool.block()
} finally { escapeDanger() }

I can now define a method fun DangerZone.swimWithFish() that is awkward to call from anywhere outside a letsGetDangerous block or another DangerZone extension method.

This is similar to the way methods that throw checked exceptions can only be called from within a try/catch block for that particular exception or a method that declares the exception.

This use of extension functions can only mimic having a single checked exception. If decorators add the ability to have multiple receivers, then you’d have the ability to mimic multiple checked exceptions.

1 Like

Oh I sparked quite the discussion here :slight_smile: Regarding contextual receivers, do you have any code example? I haven’t heard of those yet.

1 Like