Result monad with Kotlin

Sergey Opivalov
AndroidPub
Published in
2 min readSep 23, 2019

Sometimes you want your reactivex.Observable never throws onError event into the stream. The main reason is fact that onError is terminal event and your observable will not be able to publish something after that. So you need to “wrap” your data and errors to some kind of container with two states: Success(data: Data) and Error(error: Throwable).

Honestly Kotlin stdlib already has appropriate (at the first look) class: kotlin.Result. It has a fancy API with a lot of extensions that allows you to work with kotlin.Result in very elegant way.

So with kotlin.Result your infinity stream will looks further:

observable
.map{ Result.success(it) }
.onErrorReturn{ Result.failure(it) }

And when you want to handle it:

observable
.map { it.getOrDefault { Value }}

When i did it, i’ve got compilation error:

ClassCastException: <type> cannot be cast to kotlin.Result

After investigating i’ve realised that kotlin.Result can’t be used as return type.

https://youtrack.jetbrains.com/issue/KT-27105

https://youtrack.jetbrains.com/issue/KT-27586

What a disgusting ! So let’s implement Result by hands

Result implementation as algebraic data type

Most often i’ve seen Result implemented as sealed class :

sealed class Result<T> {
data class Success<T>(val data: T) : Result<T>
data class Failure(val error: Throwable): Result<Nothing>
}

Obviously, if it’s an algebraic type it rather handy works with when— compiler hints you if you’re missing handling of one of inheritors of sealed class.

The small drawback is using it with RxJava:

observable
.map { Result.Success(it) as Result}
.onErrorReturn{ Result.Failure(it) }

You need to cast all of your events to sealed class. Let’s try to avoid this.

Result implementation as a monad

Actually there is no rocket since here — i’ve took the main concept from kotlin.Result:

Using private class and constructors to not expose undesirable public API. Also you can add as much handy extensions as you need. I’ve implemented only canonical operations for the monad.

observable
.map{ Try.success(it) }
.onErrorReturn{ Try.failure(it) }
.map { materialize{ function} }

Hope it helps you guys =)

--

--