kotlin-result
kotlin-result copied to clipboard
Add tryRecoverIf and tryRecoverUnless functions
Sometimes there is a need to recover certain errors and this recovery may produce an error on its own. Existing recoverIf function doesn't fit this case because the result of transformation is wrapped in Ok. Added tryRecoverIf and tryRecoverUnless for the described case.
Could you give an example use-case where this would be used?
The naming is a little misleading currently, a function prefixed with try would imply to me that it would involve some form of try-catch statement to catch any potential exceptions. Maybe the naming is the missing piece here.
For example you try to get data from repository, don't get it and try to fetch data from remote service:
userRepository.find(userId).tryRecoverIf({ it is NotFoundError }) { userMasterSystem.getById(userId) }
I didn't want to overload fun recoverIf, that's why I have prefixed it with try. I agree that it is not very clear naming. Maybe something like runOnErrorIf will be better?
I think the names could actually be andThenRecoverIf. I've detailed how I arrived at the names below - plugging these into your example ends up with it making sense in my head and doesn't mislead about a try-catch.
Let me know if you disagree or come to a better name.
If we look at the symmetry between map and recoverIf
public inline infix fun <V, E, U> Result<V, E>.map(transform: (V) -> U): Result<U, E> {
contract {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
return when (this) {
is Ok -> Ok(transform(value))
is Err -> this
}
}
public inline fun <V, E> Result<V, E>.recoverIf(predicate: (E) -> Boolean, transform: (E) -> V): Result<V, E> {
contract {
callsInPlace(predicate, InvocationKind.AT_MOST_ONCE)
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
return when (this) {
is Ok -> this
is Err -> if (predicate(error)) {
Ok(transform(error))
} else {
this
}
}
}
Then we look at the flatMap (aka andThen):
public inline infix fun <V, E, U> Result<V, E>.andThen(transform: (V) -> Result<U, E>): Result<U, E> {
contract {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
return when (this) {
is Ok -> transform(value)
is Err -> this
}
}
If we apply the symmetry from the first snippet to the andThen, we would get:
public inline infix fun <V, E, U> Result<V, E>.andThenRecoverIf(predicate: (E) -> Boolean, transform: (E) -> Result<U, E>): Result<U, E> {
contract {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
return when (this) {
is Ok -> this
is Err -> if (predicate(error)) {
transform(error)
} else {
this
}
}
}
Which is identical to your tryRecoverIf.
This would result in your example producing the following code:
userRepository
.find(userId)
.andThenRecoverIf({ it is NotFoundError }) { userMasterSystem.getById(userId) }
andThenRecoverIf sounds nice to me. Should I move it to And.kt?
Yeah I think that makes sense. I feel like the lib is quickly exploding with the if/unless variations for every function so it's likely these will be the last of their type.
Seems like one of the tests has failed on macos-11
Yes, I missed it, thank you! Hope no errors now =)
Merged in d4414b1a086cb219795d649fddf79bbd4c9a4c63