BTW is there curretly any discussion going on abou...
# stdlib
l
BTW is there curretly any discussion going on about adding some convenience extension functions on e.g.
List<Result<T>>
(when using
runCatching {}
) which would help be execute some action in case there have been some failures etc.? Since I think this is (or will be) a very common design pattern - similarly as there is a convenience ext. function for
List<Deferred<T>>.awaitAll()
. Recently I was thinking it could be done in the similar fashion as the current
.onFailure()
and
.onSuccess()
except that the lambda would take
List<Throwable>
or
List<T>
as an argument (respectively). Currently what I need to do on such list, is: (1)
filter { it.isFailure}
, then (2) I need to check that it is not empty, and not until now (3) I can do something with it. With the above proposal the first 2 steps would be unnecessary and I would be able to define just the part which would execute only if there was at least 1 failed Result. Similarly it could work for successful Result values.
g
Instead of isFailure you can use
mapNonNull { it.getOrNull() }
Also awaitAll have different semantics from proposed one and will throw an exception if any of Deffered is failed So it closer to:
map { it.getOrThrow() }
So maybe some shortcut would be useful but not much better also less flexible
l
Let me demonstrate more clearly what I mean. Imagine we have this code:
Copy code
val results = (1..5).map {
        runCatching {
            if (it % 2 == 0) throw Exception("Oops!") else it
        }
    }

    val failed = results.filter { it.isFailure }

    if (failed.isNotEmpty()) {
        println("There were ${failed.size} failures and the first one was: ${failed.first().exceptionOrNull()}")
    }

    println("Succeeded were:")
    results.filter { it.isSuccess }.forEach { println(it.getOrNull()) }
Wouldn't it be much better if I could write it e.g. like this?
Copy code
(1..5).map {
        runCatching {
            if (it % 2 == 0) throw Exception("Oops!") else it
        }
    }
    .onFailed { println("There were ${it.size} failures and the first one was: ${it.first()}") }
    .onSucceeded { succeeded ->
        println("Succeeded were:")
        succeeded.forEach { println(it) }
    }
g
So if you really want to separate all errors from all results and use them, yes, make sense to write such function
I would just write it more like
groupBy
I’m still not sure that such function should be in stdlib, because it’s easy to write yourself and function is quite specifc, sometimes you care about all errors, sometimes only about first one, sometimes you want to get list of results and list of errors, sometimes one Result
For errors someone may want to use something like CompositeException from RxJava So many different, very specific options
Also you can rewrite your example like this:
Copy code
val succeed = (1..5).mapNonNull {
            if (it % 2 == 0) { 
               println("Failure of operation $it") 
               null
            } else {
                it
            }
        }
    }
println("Succeeded were:")
 succeed.forEach { println(it) }
Much more concise and simple, no Result at all
Of course it can be not so straight forward, but it again depends on case and can be simplified without ton of chaining operators
l
After some time I found one use case, where
.onFailed { }
as I was thinking about it cannot be used. So I am still with your
mapNotNull
solution (it saves one
filter
call 😁), so thanks so far 🙂
👍 1
d
You could always use
val (successful, failed) = results.partition { it.isSuccess }
, instead of two filters...
l
Well, that would give me
Pair<List<Result<T>>, List<Result<T>>>
. If it gave me
Pair<List<T>, List<Throwable>>
then I would be good with it 😁.
d
I meant instead of the two filters above in the original code 😉
l
Alright 🙂 . BTW: Perhaps something like this could be added to Kotlin stdlib:
inline fun <T: Any> Collection<Result<T>>.partitionByResult() = mapNotNull { it.getOrNull() } to mapNotNull { it.exceptionOrNull() }
Then having this example:
Copy code
fun main() {
    val results = listOf(
        runCatching { "Hi" },
        runCatching { throw Exception("Oops") }
    )
    
    val (ok, errors) = results.partitionByResult()
    
    println(ok)
    println(errors)
}
The output is:
Copy code
[Hi]
[java.lang.Exception: Oops]
i
That looks interesting, I'd rather call it
partitionBySuccess
. Could you open an issue for this proposal?
l
@ilya.gorbunov Thanks Ilya for your support 🙂 . The proposal has been created here: https://youtrack.jetbrains.net/issue/KT-28004