I’m struggled with a problem around using kotlin’s...
# announcements
e
I’m struggled with a problem around using kotlin’s built-in
Result
class. I’m trying to go through a process which would look like: • do step 1, if fail then return with an error • do step 2 using step 1 result, if fail then return with an error • … so far what I coded is kind-of ugly:
Copy code
runCatching { step1() }
    .fold(
        onFailure = { myCustomError("step1") },
        onSuccess = { step1Result -> 
            runCatching { step2(step1Result) }
                .fold(
                    onFailure = { myCustomError("step2") },
                    onSuccess = { ... 
        ...
     )
I’m pretty sure there’s a better way to do this - any suggestions?
n
Copy code
val result1 = try { step1() } catch (e: Exception) { myCustomError("step1") }
val result2 = try { step2(result1) } catch (e: Exception) { myCustomError("step2") }
but with like a dozen more newlines in there. doesn't use Result but maybe you can say
.get()
to force it to throw or something.
o
Yes, I've also faced this problem, and solved this by the following ext function
Copy code
fun <T, R> Result<T>.letIfSuccess(block: (T) -> Result<R>): Result<R> = 
    when(this) {
        is Result.Failure -> this
        is Result.Success -> block(this.data)
    }
Now, you can chain your results with letIfSuccess, and prevent the nesting hell
Note: I am using this extension function on my own custom Result type. You can apply it to kotlin.Result as well
Now my control flow is really clean without ugly early returns or throwing exceptions
n
I think the simplest is simply to do
runCatching
o
@Nir he is already using runCatching.
n
I'm not sure how it ended up so ugly with runCatching
typically the idea is that you're calling functions that return result
Copy code
val r = runCatching {
    val x = someFunc().getOrThrow()
    val y = someFunc2().getOrThrow()
}
etc
that's the whole point of using runCatching; you just use getorThrow
you don't use fold
o
Not true, runCatching + fold is a nice pattern of transforming a throwing function to a one that returns a result
Its a quick bridge from exception world to result world
Im using runCatching and never used getOrThrow() myself
n
how is it a nice pattern?
If you use
fold
you don't really need runCatching
o
Function foo() returns a string or throws. When I wrap foo() with runCatching, it doesnt throw anymore, and returns a result<String> instead.
n
yes...
o
Then now, I can use fold to react to the result if its a failure or success
n
Sure, you can, it's just a matter of the best tool for the job. the point of runCatching is that it lets you chain throwing things (or Results since they easily can throw)
o
Why chain? You can use runCatching to simply wrap a throwing function with a result
So you don't have to use try {} catch
n
nobody's suggesting using try catch to begin with
o
Ok but I don't understand your point then. It looks like you are saying that runCatching { foo() }.fold(...) is of no need, and you could foo() directly instead?
n
I was talking about using runCatching for the purpose of composing function that error
o
Not sure what function that error means
n
functions that can error? throw or return Result?
o
yes, but we all agree on that I think we use runCatching for error handling
n
let's say you use your approach on the OP code
you're going to have the original function call, then
fold
, and then letIfSuccess
3 calls for every step of the computation
sorry, the original function call, runCatching, then fold, then letIfSuccess
assuming the original function throws
o
You dont need fold with letifsuccess
n
where are you transforming to the custom error type?
none of the other places let you do that transformation
The entire problem here is the custom error transformations. Otherwise, you don't need anything other than a single runCatching around the whole computation
o
Yeah I think the example pseudo code is too pseudo for us to really understand what he wanta
n
Copy code
val x = runCatching {
     step2(step1())
}
No, not at all, it's very clear what he wants
No, not at all, it's very clear what he wants
He wants to transform the errors from step1 and step2 into his own custom errorrs
Copy code
val x = runCatching {
    val first = runCatching { step1() }.getOrElse { // throw custom exception type }
    val second = runCatching { step2(first) }.getOrElse { // throw custom exception type }
}
o
Then what does his function return?
Any
? Or is it a supertype of the custom error type and succeasfull path return type
n
@Endre Deak sorry we wandered around a bit and I didn't fully 7understand at first, but I think that code is pretty nice
His function returns a result
o
Your last example is throwing custom exxeptions? While OP doesnt want to throw custom exceptions, he wants to return Custom Error objects
n
It's going to throw and immediately get caught by the runCatching
the block of code I showed will not throw and
x
will be a Result, which is what OP wants
o
Yeah but it will still be type of Excpetion, not custom error like OP indicated
n
That's a good point, the OP's code isn't fully clear in the sense that the onFailure and onSuccess don't seem to return the same type
so the OP will get an Any
o
Yeah its too pseudo at the moment
n
That said, maybe this is closer to what he meant, and `x`'s type is always better than having an Any
you can always try to cast the error type back, like you would cast an Any, and the successful result is handled
e
Ok, to make the OP more clear -
myCustomError
would return with a custom result object indicating the success of the entire process or the failure (at which step and what the issue was). So if I’m flattening out, what I want to achieve nicely is something like:
Copy code
data class MyCustomResult(
   val code: ResultCode,
   // .. details)

val firstRes = runCatching { first() }
if (firstRes.isFailure) {
    return MyCustomResult(STEP_1, first.exceptionOrNull())
}

val secondRes = runCatching { second(firstRes.getOrNull()!!) }

if (secondRes.isFailure) 
...
...
n
What's the overall return type?
e
MyCustomResult
n
Then just do a run block
With runCatching around the throwing function
And return in the getOrElse block
e
ohh
n
You can write a tiny function that just combined runCatching with getOrElse
Could call tryOrElse
Although maybe it's not worth it
Since you need to pass two lambdas
e
but
getOrElse()
returns with
Any
so an idea of doing something like
Copy code
runCatching { first() } 
.getOrElse { firstError() } 
.let { f -> second(f) } 
.getOrElse { ... } 
...
won’t work because
f
is
Any
instead of the result type
n
The thing is that your custom result type is kind of misnamed, it only holds errors, not successful results
Ah, i see
Can you make your custom result type inherit from Throwable?
Sorry, custom error type
And then just use
Result
instead of MyCustomResult
otherwise, you'll need to reimplement a lot of
Result
-like machinery in MyCustomResult