michaelbull / kotlin-result

A multiplatform Result monad for modelling success or failure operations.
ISC License
1.02k stars 57 forks source link

Recovery extension #87

Closed caiofaustino closed 1 year ago

caiofaustino commented 1 year ago

More of a general question rather than an issue.

We use andThen extension quite a lot in our project to chain logic events. I'm not very experienced in the functional world but If I would explain it in words is sort of "on a success execute this operation and transform into a new Result type"

Is there already any extension that would basically accomplish the reverse? Execute on an error and transform the result?

I'm facing a scenario where I want to try to recover from a failure. But the recovery itself could also still fail. So the code we have atm looks something like this:

Using these types as an example

fun foo(): Result<Int, String> = Err("FooError")
fun recoverFoo(): Result<Int, Int> = Ok(1)

We have this fold:

foo().fold(
    success = { Ok(it) },
    failure = {
        recoverFoo()
            .mapError { "FooRecoveryError" }
    },
)

And I was hoping there would be a cleaner way to write it as maybe something like this:

foo().recoverError {
    recoverFoo()
        .mapError { "FooRecoveryError" }
}

And I guess if the method existed it would look something like this:

public inline infix fun <V, E, U> Result<V, E>.recoverError(transform: (V) -> Result<V, U>): Result<V, U> {
    contract {
        callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
    }

    return when (this) {
        is Ok -> this
        is Err -> transform(value)
    }
}

So I guess my actual question is, with the existing extensions, is there a cleaner way to write this recovery scenario instead of using fold? I looked at mapError but it forces the outcome to also be an Error, and that's not really the idea here, and I wasn't able to see any other alternatives.

michaelbull commented 1 year ago

I think you want orElse


public inline infix fun <V, E, U> Result<V, E>.recoverError(transform: (V) -> Result<V, U>): Result<V, U> {
    contract {
        callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
    }

    return when (this) {
        is Ok -> this
        is Err -> transform(value)
    }
}

This example doesn't make much sense to me. If the result is an Err then V is always null, so your transform cannot be V -> anything.

caiofaustino commented 1 year ago

Ahhh yes, the orElse is what I was looking for! Sorry I forgot to change the types in my attempt implementation of the recoverError. Thanks a lot, sorry if this was an obvious question.

michaelbull commented 1 year ago

No worries, feel free to open another issue if you get stuck again 👍