Reputation: 545
Question for you smart people. I'm glad to see some functional objects in Kotlin such as Result, but I'm struggling how to use it effectively. Here's a couple of patterns that I came up with, but their must be better.
I'll just call this the imperative pattern for calling a series of functions that return a Result<*>
instance:
fun myFunc(): Result<String> {
return runCatching {
val x = f1().getOrThrow()
val y = f2(x).getOrThrow()
val z = f3(y).getOrThrow()
forTheWin(x, y, z).getOrThrow()
}
}
This works and it's certainly easy for imperative programmers to pick up and use.
However, it doesn't feel right to me. I would prefer some sort of chaining operation such as f(g())
, but what do you do with a Result<Result<T>>
, so I came up with some simple flattening functions:
fun <T> Result<Result<T>>.flatten(): Result<T> = when {
isSuccess -> getOrNull()!!
else -> Result.failure(this.exceptionOrNull()!!)
}
fun <T, R> Result<Result<T>>.flatmap(f: (T) -> R): Result<R> = this.flatten().map(f)
fun <T> Result<T>.mapFailure(f: (Throwable) -> Throwable): Result<T> =
this.recoverCatching { throw f.invoke(it) }
such that f(g()).flatten()
returns a Result<T>
. (I've tried and failed to write a flatten()
that will flatten a Result of arbitrary depth.)
Taking the above example, I can rewrite it as:
fun myFunc(): Result<String> {
return f1().map { x ->
f2(x).flatMap { y ->
f3(y).flatMap { z ->
forTheWin(x, y, z)
}.flatten
}
which still isn't satisfying as I usually have to do some careful massaging to get the map, flatmaps, and flatten to balance into a simple Result<T>
result. If I could just call a single flatten()
, it would go a long way to being a usable pattern.
Question: Does anybody have a pattern to chain functions that return Result types? Or, are there better functional objects in Kotlin?
Upvotes: 0
Views: 662
Reputation: 1163
inline fun <T, R> Result<T>.flatMap(f: (T) -> Result<R>): Result<R> = when {
isSuccess -> (getOrNull() as T).let(f)
isFailure -> this as Result<R>
else -> {
throw IllegalStateException("Unknown state")
}
}
fun myFun(): Result<String> {...}
fun anotherFun(s: String): Result<Int> {...}
fun andAnotherOne(i: Int): Result<Boolean> {...}
val result: Result<Boolean> = myFun().flatMap(::anotherFun).flatMap(::andAnotherOne)
Upvotes: 0