michaelbull / kotlin-result

A multiplatform Result monad for modelling success or failure operations.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add a function combining andThen with runCatching

oddsund opened this issue Β· comments

Hello,

First of all, sorry for not fixing that typo in the PR i sent you πŸ˜…

A common pattern I see when wrapping library code is the following;

.andThen {
	runCatching {
		// lib code
	}
}

The following creates an annoying typewarning, and looks wonky;

.map {
   // code
}.runCatching {
	if(this is Err) {
		throw this.error
	} else {
		// lib code
	}
}

Would you be willing to accept a PR for something like this? Name TBA, fire if you have any suggestions.
Also, would you place such a function in And.kt or Factory.kt?

.andThenCatching {
	// lib code
}
.runCatching {
	if(it is Err) {
		throw it
	} else {
		// lib code
	}
}

This part of your example doesn't make sense to me. What is it in this context?

The current implementation of runCatching has no argument, therefore has no it:

public inline fun <V> runCatching(block: () -> V): Result<V, Throwable>

Generally to solve your problem I would make my own function that calls runCatching on the library code, rather than nesting it as per your example.

sealed class StepError {
    object One : StepError()
    object Two : StepError()
    data class Three(val message: String?) : StepError()
}

fun runAllSteps(input: Int): Result<Int, StepError> {
    return stepOne(input)
        .andThen(::stepTwo)
        .andThen(::stepThree)
}

fun stepOne(input: Int): Result<Int, StepError.One> {
    TODO()
}

fun stepTwo(input: Int): Result<Int, StepError.Two> {
    TODO()
}

fun stepThree(input: Int): Result<Int, StepError.Three> {
    return callVendor(input).mapError { StepError.Three(it.message) }
}

fun callVendor(input: Int): Result<Int, Throwable> = runCatching {
    TODO()
}

In the above example, the runAllSteps function is transparent about the steps in the process, but stepThree hides the implementation detail of calling a third party library in its body. If you decide at a later point to change which library step three is calling behind-the-scenes, you only need to edit stepThree and your runAllSteps function remains as it was.

Sorry, updated the example. The example used the follwing call;

public inline infix fun <T, V> T.runCatching(block: T.() -> V): Result<V, Throwable> {

So it was supposed to be this, as one would have if the call was chained.

I've already solved it (kind of) like that a few times, so I guess I'll keep going that way. Still learning, so thanks for the tip!

No worries. Generally I'm apprehensive to changes like this because we would effectively end up going through the whole library adding xCatching variants for every function, in order for the library to comprehensively handle the cases where you're working with code that throws instead of returning a Result. I'd rather keep the library more focused and encourage people to be writing their code first-and-foremost with Result in mind, and only using things like runCatching when interacting with code they can't control.

Feel free to reach out with more cool ideas though, I'm open to changing my mind!