Result monad with Kotlin
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 =)