GithubHelp home page GithubHelp logo

michaelbull / kotlin-result Goto Github PK

View Code? Open in Web Editor NEW
941.0 17.0 54.0 909 KB

A multiplatform Result monad for modelling success or failure operations.

License: ISC License

Kotlin 100.00%
kotlin result monad either type class functional-programming functional fp ios

kotlin-result's People

Contributors

05nelsonm avatar avently avatar berikv avatar bitpogo avatar deryeger avatar dimsuz avatar globegitter avatar gregoryinouye avatar gsteckman avatar jhabkin avatar jvanderwee avatar kevinherron avatar kirillzh avatar mguelton avatar michaelbull avatar mrbergin avatar munzey avatar nimelrian avatar pablisco avatar peter-cunderlik-kmed avatar yuitosato avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kotlin-result's Issues

flatMap

I can't see a flatMap function or its equivalent which is typical for monads. It should work like Haskell's >>=.

My suggested implementation:

infix inline fun <V, E, U> Result<V, E>.flatMap(transform: (V) -> Result<U, E>) : Result<U, E> {
    return when (this) {
        is Ok -> transform(value)
        is Err -> this
    }
}

Also flatMapError function could be implemented in simillar way. If you like the idea, I will implement them along with unit tests and create a pull request.

Thanks!

Multiplatform ArtifactNotFouundException

I'm trying to import com.michael-bull.kotlin-result:kotlin-result:1.1.8 in the commonMain source set in a multiplatform project, but the gradle build is failing with this stacktrace:

Gradle import errors
project ':web': Unable to build Kotlin project configuration
Details: org.gradle.internal.operations.BuildOperationQueueFailure: There was a failure while populating the build operation queue: Could not find kotlin-result-1.1.8-samplessources.jar (com.michael-bull.kotlin-result:kotlin-result:1.1.8).
Searched in the following locations:
https://repo.maven.apache.org/maven2/com/michael-bull/kotlin-result/kotlin-result/1.1.8/kotlin-result-1.1.8-samplessources.jar
Caused by: org.gradle.internal.resolve.ArtifactNotFoundException: Could not find kotlin-result-1.1.8-samplessources.jar (com.michael-bull.kotlin-result:kotlin-result:1.1.8).Searched in the following locations:
https://repo.maven.apache.org/maven2/com/michael-bull/kotlin-result/kotlin-result/1.1.8/kotlin-result-1.1.8-samplessources.jar

I'm using the same dependency in another (JVM-only) project and it works perfectly. Is there something I'm missing with multiplatform configuration that will solve this? Thanks!

iosSimulatorArm64 and macOsArm64 support

Are there any plans to include iOS Simulator and macOS running on Apple Silicon as kotlin native targets (iosSimultorArm64() and macOsArm64() targets)? This would greatly improve experience of KMM projects developers using this great library :)

Use of runCatching and coroutines breaks structured concurrency

Since runCatching catches all exceptions, it catches CancellationException which prevents any coroutines launched within the runCatching block from being cancelled when their enclosing CoroutineScope is cancelled.

This is not, strictly speaking, an issue with kotlin-result but it caught me by surprise so I thought it would probably be good to warn others of the potential gotcha.

I propose:

  1. A warning in the kdoc comment, pointing out the problem or,
  2. 1 + a new function in kotlin-result-coroutines called something like runSuspendCatching() which rethrows CancellationException.

I've already written a version of runSuspendCatching() and a test case that demonstrates the behaviour in my own project so I'm more than happy to submit a PR if you want.

The same issue has been raised for the stdlib runCatching at Kotlin/kotlinx.couroutines#1814 but it doesn't seem like the Kotlin devs thinks it's much of a problem. I disagree and thought maybe we could at least improve things here!

Next release?

First of all, thanks for the nice result library you provide!

I would like to use the recoverIf function, which was introduced in one of the last PRs.

Is there a release due in the near future?

Should we offer a binding that can bind results of any Error type?

i.e. should we change or offer the following as part of the api:

fun <V> binding(block: ResultBinding.() -> V): Result<V, *>

This would allow 2 things:

  1. no more specifying the types when declaring a binding block:
val result = binding {
  val x = doSomething().bind()
  val y = doSomethingElse().bind()
  x + y
}
  1. doSomething and doSomethingElse can have error types that dont extend from the same type.
    The downside of this is that now the ide will see the type of result in the above example as Result<Int, *>
    But is that so bad?

If we wanted to add this, would it make more sense to replace the current binding with this? or offer a second function. What would they be named? bindingAny? bindingExplicit?

Please Refactor to Use Kotlin Result For 1.5+

So with Kotlin 1.5+, the Result type is finally "fully featured". I say that because, with the ability to use it as the return type for methods, it is truly viable as a functional error handling solution. However, having used Kotlin's Result and your Result, I must say that I like your API and feature set far better. However, a lot of the signatures class with Kotlin's (ie, their Result vs your Result) clash, so to use your library I typealias everything (ie, KtResult).

I think it would be wonderful if you were to refactor this to be built on top of the default Kotlin result, with all your great features (ie, flatMap, orElse, etc) as extension functions to it. I can see some challenges, namely that Result is a final class that cannot be extended (for Ok/Err), but I think that the outcome would be worth the effort.

Let me know your thoughts on this. I may be able to find some time to help you if you are interested.

PS. Obviously, given the scale of the breaking changes, you would have to either bump the major version, provide legacy API support, or create a separate 1.5+ artifact.

`orElse` for Error Type Transformation

Hello.

Currently, Result.orElse has the following signature:

<V, E> Result<V, E>.orElse(transform: (E) -> Result<V, E>): Result<V, E>

However, it does not allow for changing the type of the error. Rust's or_else can change it, so how about modify it to align with that capability?

<T, E, F> Result<T, E>.orElse(transform: (E) -> Result<T, F>): Result<T, F>

thanks!

Mapping a failure to success

This is probably not an issue. Its more of a query i have. I very well could be misusing, misunderstanding or not aware of something.

The use case is if the api call fails i want it to successfully return the cache.

Much like andThen allows the transforming of one thing to another contained withing the Result type. What i feel i am looking for is a way to "andThen the error". So i can transform the E into a success using Ok(SomeType)

I can't find the function though i could just be using it wrong/this is not the correct practice.
For now i'm letting my Error contain any type so it can optionally return the result i want.

Thanks in advance, really loving using kotlin-result so far.

Kotlin multi-platform support

This question was already raised (here #5), but still, I am not able to use this library in the Multiplatform project. I think this is due to differences between the android library's build.gradle and the multiplatform one. In the MP library you need to specify kotlin("multiplatform") plugin and divide code into source sets (for that specific library you will need only common source set). You can read more about [https://kotlinlang.org/docs/tutorials/mpp/multiplatform-library.html](Multiplatform Kotlin library) at kotlintlang.org

I've tested this library with MP "structure" and it seems to work, so if you are interested in supporting MP I can create PR for this and you will check out code.

Recovery extension

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.

e: Could not load module <Error module>

I am getting an obscure error message when attempting to build my Android application after refactoring a Repository interface to return Result: e: Could not load module <Error module>

It's so vague that I can't think of where to look for what could be causing the error. I found this thread which suggests the error is caused by type inheritance failing.

Implement method into_ok_or_err for Result<T, T>

I think it'd be nice to be able to write something like

val a = res.map { it.joinToString("\n") }.into_ok_or_err()

instead of

val a = when (res) {
    is Ok -> res.unwrap().joinToString("\n") // -> String
    is Err -> res.unwrapError() // -> String
}

This feature already exists in Rust Nightly.

Missing kotlin-result-mingwx64 on maven

Hello, I'm trying to build a KMP lib but I can't build the native variant (I've been able to build the other variants fine)

Execution failed for task ':compileKotlinNative'.
> Could not resolve all files for configuration ':nativeCompileKlibraries'.
   > Could not resolve com.michael-bull.kotlin-result:kotlin-result:1.1.11.
     Required by:
         project :
      > No matching variant of com.michael-bull.kotlin-result:kotlin-result:1.1.11 was found.
      ...

From what I understand, the mingwx64 variant of kotling-result seems to be missing for maven
https://search.maven.org/search?q=g:com.michael-bull.kotlin-result

Do you plan to add the mingwx64 artifact on maven in the future ?

About component1() and 2

Hi,

I was looking at the code and it seems that the component1() and component2() methods in the Result class are not really used anywhere.

I was wondering if they have any use that I'm failing to see here or if they could be removed.

Thanks.

Multi-platform support?

Hi there, would you provide us any instruction on how to use the library in a multi-platform (JVM+JS) project?

Discuss: Use coroutines instead of exceptions for binding

Hi!

I would've posted this in discussion if it was available, but I was curious if it had ever been considered to use coroutines instead of exceptions for the binding blocks.

Using exceptions has always seemed slightly error prone to me since all it takes is for someone to catch a generic exception and you now potentially have unexpected behavior. In addition stacktraces can be slightly expensive to produce.

On the flip side, coroutines have their drawbacks as well. For example, the stack traces can sometimes be confusing and there are limitations for where you can actually use bind.

For example I made a proof of concept and tried it out in a repo that made decent usage of binding. The issues are that any function that is not inline or suspend will produce a compilation error because you need to be within the custom coroutine context.

A map function is okay because it's inline, but a sequence function is not

listOf(1, 2, 3).asSequence().map { 
    provideX().bind()
}
Suspension functions can be called only within coroutine body

The other issue I discovered is when the binding context is put into the receiver of another function it fails as well (this is only an issue when restricting the scope of what suspend functions can be called from your scope).

run { 
    provideX().bind()
}

doesn't work but

kotlin.run { 
    provideX().bind()
}

In conclusion, I'm not convinced yet whether one solution is better than the other. I think the coroutine based approach has some benefits over the exception based approach, but it's slightly more restrictive in how you use it. I'm curious to know what you and others think! Thanks for your time.

Here's a link to my POC if you want to try it out. I made both a pure kotlin.coroutines version and a kotlinx.coroutines version.

dronda-t/kotlin-result@master...dronda-t:kotlin-result:dronda/kotlin-coroutines

Using fun doSomething(): Result<String, MyException> generates an error

I have a method with this signature:

class MyException(override val message: String?, override val cause: Throwable?) : Exception(message, cause)

  fun doSomething(): Result<String, MyException> {
    val x: Result<String, MyException> = Result.of {
      try {
        "34"
      } catch (e: Exception) {
        throw MyException("some message", e)
      }
    }
  }

I get a compilation error saying:

Error:(22, 19) Kotlin: Type inference failed. Expected type mismatch: inferred type is Result<String, kotlin.Exception /* = java.lang.Exception */> but Result<String, MyException> was expected

is it possible the problem is the definition of Result?

sealed class Result<out V, out E> {
    companion object {

        /**
         * Invokes a [function] and wraps it in a [Result], returning an [Err]
         * if an [Exception] was thrown, otherwise [Ok].
         */
        inline fun <V> of(function: () -> V): Result<V, Exception> {
            return try {
                Ok(function.invoke())
            } catch (ex: Exception) {
                Err(ex)
            }
        }
    }
}

It should not be something like:

inline fun of(function: () -> V): Result<V, out Exception> {

or perhaps I am missing something? I am new to kotlin-result and kotlin. So I cannot say...

Can't find artifact

First of all, thanks for the great work!

As far as I understand I should be able to find the current version (com.michael-bull.kotlin-result:kotlin-result:1.1.8) at maven central.
Unfortunately when looking there I only find the POM and checksum files, but not the JAR. Same for 1.1.7.
Only for 1.1.6 I'm able to find the actual JAR on mane central.

Is this a misunderstanding from my side or where can I find the current JAR?

Assertion helpers for tests

Would you be open to adding helper functions for using Results in tests?

For example:

inline fun <reified V, reified E> Result<V, E>.assertOk(): V {
    assertIs<Ok<V>>(this)
    return value
}

Usage:

fun doSomething(): Result<String, Exception> {
    // ...
}

@Test
fun test() {
    val success: String = doSomething().assertOk()
}

Documentation of order of elements in Iterable.kt

Question rather than an issue: the docs in Iterable.kt for e.g. getAll() mention that the order is preserved. Other methods like combine() don't mention anything about the order.

Is the order guaranteed and it's just that the docs are a bit inconsistent between methods or the order for some methods is not guaranteed?

Gradle

Copying the following lines:

maven { url "https://jitpack.io" }
compile 'com.github.michaelbull:kotlin-result:1.1.0'

into the repositories and dependencies respectively generated a build error in Gradle.

Build error is as follows:
Warning:root project 'my_project': Unable to build Kotlin project configuration
Details: java.lang.reflect.InvocationTargetException: null
Caused by: org.gradle.api.artifacts.ResolveException: Could not resolve all dependencies for configuration ':compileClasspath'.
Caused by: org.gradle.internal.resolve.ModuleVersionNotFoundException: Could not find com.github.michaelbull:kotlin-result:1.1.0.
Searched in the following locations:
https://repo1.maven.org/maven2/com/github/michaelbull/kotlin-result/1.1.0/kotlin-result-1.1.0.pom
https://repo1.maven.org/maven2/com/github/michaelbull/kotlin-result/1.1.0/kotlin-result-1.1.0.jar
https://jitpack.io/com/github/michaelbull/kotlin-result/1.1.0/kotlin-result-1.1.0.pom
https://jitpack.io/com/github/michaelbull/kotlin-result/1.1.0/kotlin-result-1.1.0.jar
Required by:
project :

Rename Result to Either

This is a great library and I'm using it for back- and frontend Kotlin code likewise!
One issue I have with it is that the IDE usually picks kotlin.Result as a default for the import and I have to manually add the com.github.michaelbull.result.Result import. Using Either would be closer to the standard for what Result represents (not just closer, exactly spot on ;-). Thanks for considering.

Best practices for integrating with Flow<T>

I'm not really sure the best way to achieve this. It could very well be ignorance on my part.

I have an Android room table i want to Flow on. Something like: Flow<Vehicle>
First i need to get the userVehicleId from the User Repository. That isn't a flow, it returns Result<Int, Unit>

The function i have atm looks like this:

suspend fun getUserVehicle(): Flow<Result<Vehicle, Unit>>{ 
   return userRepo.getUserVehicle()              // returns `Result<Int, Unit> `
        .andThen{ id ->
            Ok(vehicleDao.flowOnUserVehicle(id)) // returns Flow<Vehicle, Unit>
        }
}

This isn't valid and won't compile. Since I'm returning Result<Flow<Vehicle, Unit>

Thanks in advance!

Proposal: add getErr() function to UnwrapException

When creating a domain entity with more than five fields (so zip is not available), the best pattern I've found goes like this:

data class T(val a: A, val b) {
  companion object {
    fun create(x1 : String?, x2: String?) : Result<T, DomainMessage> =
      try {
        Ok(T(
          A.create(x1).unwrap(), 
          B.create(x2).unwrap()));
      } catch (e:Exception) {
        Err(ParseError("error: ${e.message}");
      }
}   

What I'd like to be able to do is return the original DomainMessage from the catch block:

      } catch (e: UnwrapException) {
        e.getErr()
      }

Does this seem reasonable to you?

---- Edit: in proposed catch block, change Exception to UnwrapException

Consider using consistent "error" naming over "failure"?

I've noticed that certain APIs use "failure" terminology (onFailure, fold(failure = ), mapBoth(failure =)), whereas others use "error" terminology (mapError, getError, toErrorIf and more).

It looks like it would be more consistent to use "error" terminology across the board: onError, fold(error = ), mapBoth(error = ) + any other APIs that I might be missing.

Binding doesn't support coroutines

I was excited to try out the binding block in our codebase but it doesn't support suspending functions. We currently use Result heavily with coroutines so this was a bit of a bummer. Is there any likelihood of this being added in the future? I'm not sure if it's out of scope for this project. I'm happy to raise a PR if you think it would be a worthwhile addition.

Add a function combining andThen with runCatching

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
}

Mapping a failure to success given a predicate

While wrapping calls to an external api, one pattern emerged - recoverIf. So it would be similar to toErrorIf and toErrorUnless, and would allow us to selectivly recover from known errors.

Is there any reason that this is not implemented? If not, I'll try to provide a PR during the weekend.

Consider adding zipOrAccumulate?

Have you considered implementing a zipOrAccumulate method, which unlike zip, takes all of the Err from the Result arguments and returns them as Result<T, List<Err>>?

zip returns the first Err from the Result arguments, which can be inconvenient when we want to return multiple errors during validation.
For instance, when creating a user instance, if both the name and email address are invalid, we would want to return two errors.

class User(val name: Name, val email: Email) {
  companion object {
    fun create(name: String, email: String): Result<User, List<UserCreateError>> {
      ...
    }
  }
}

Arrow's Either has a zipOrAccumulate method that can solve this problem.

https://arrow-kt.io/learn/typed-errors/working-with-typed-errors/#accumulating-different-computations
https://github.com/arrow-kt/arrow/blob/cb033637ab6b6f1f134b1924c948d6638cc5acf4/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Either.kt#L1499

As the implementation is not overly complicated, would you consider including it in kotlin-result?

Here's an example implementation: ZipOrAccumulate.kt

/**
 * Apply a [transformation][transform] to ten [Results][Result], if both [Results][Result] are [Ok].
 * If not, the all arguments which are [Err] will propagate through.
 */
public inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, E, Z> zipOrAccumulate(
    producer1: () -> Result<T1, E>,
    producer2: () -> Result<T2, E>,
    producer3: () -> Result<T3, E>,
    producer4: () -> Result<T4, E>,
    producer5: () -> Result<T5, E>,
    producer6: () -> Result<T6, E>,
    producer7: () -> Result<T7, E>,
    producer8: () -> Result<T8, E>,
    producer9: () -> Result<T9, E>,
    producer10: () -> Result<T10, E>,
    transform: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) -> Z,
): Result<Z, Collection<E>> {
    contract {
        callsInPlace(producer1, InvocationKind.EXACTLY_ONCE)
        callsInPlace(producer2, InvocationKind.AT_MOST_ONCE)
        callsInPlace(producer3, InvocationKind.AT_MOST_ONCE)
        callsInPlace(producer4, InvocationKind.AT_MOST_ONCE)
        callsInPlace(producer5, InvocationKind.AT_MOST_ONCE)
        callsInPlace(producer6, InvocationKind.AT_MOST_ONCE)
        callsInPlace(producer7, InvocationKind.AT_MOST_ONCE)
        callsInPlace(producer8, InvocationKind.AT_MOST_ONCE)
        callsInPlace(producer9, InvocationKind.AT_MOST_ONCE)
        callsInPlace(producer10, InvocationKind.AT_MOST_ONCE)
        callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
    }

    val result1 = producer1()
    val result2 = producer2()
    val result3 = producer3()
    val result4 = producer4()
    val result5 = producer5()
    val result6 = producer6()
    val result7 = producer7()
    val result8 = producer8()
    val result9 = producer9()
    val result10 = producer10()

    return if (
        result1 is Ok &&
        result2 is Ok &&
        result3 is Ok &&
        result4 is Ok &&
        result5 is Ok &&
        result6 is Ok &&
        result7 is Ok &&
        result8 is Ok &&
        result9 is Ok &&
        result10 is Ok
    ) {
        Ok(
            transform(
                result1.value, result2.value, result3.value,
                result4.value, result5.value, result6.value, 
                result7.value, result8.value, result9.value,
                result10.value,
            ),
        )
    } else {
        Err(
            listOf(
                result1, result2, result3, result4,
                result5, result6, result7, result8,
                result9, result10,
            ).mapNotNull { (it as? Err)?.error },
        )
    }
}

public inline fun <T1, T2, E, Z> zipOrAccumulate(
    producer1: () -> Result<T1, E>,
    producer2: () -> Result<T2, E>,
    transform: (T1, T2, T3) -> Z,
): Result<Z, Collection<E>> { ... }

public inline fun <T1, T2, T3, E, Z> zipOrAccumulate(
    producer1: () -> Result<T1, E>,
    producer2: () -> Result<T2, E>,
    producer3: () -> Result<T3, E>,
    transform: (T1, T2, T3) -> Z,
): Result<Z, Collection<E>> { ... }

public inline fun <T1, T2, T3, T4, E, Z> zipOrAccumulate(...

runCatching catches Throwable?

Hi, in Java, it is generally consider a bad practice to catch Throwable instead of Exception due to Throwable being the super class of Error which represents lower level errors like OutOfMemory etc.
May I know is there any reason why runCatching is catching Throwable instead of Exception?

Extensions for Kotlin Flow?

Have you considered providing extensions for Kotlin Flows, specifically, Flow<Result<V, E>>? I'm making heavy use of these "flows-of-result" and I have some helpers of my own. However, I feel that it would be much nicer to have binding-style utilities that know about Flow. Here are some examples of helpers that we have in our project (admittedly, some of these can have better names!):

Some variants of Flow's combine operators that know about Flow<Result<V, E>>

/**
 * Variant of Kotlin's [combine] that makes it easier to work with flows of [Result].
 *
 * Use this if [transform] never returns an error. If your transform might return an error, consider using [combineResultWithErr] instead.
 */
fun <T1, T2, E, R> combineResult(
    flow: Flow<Result<T1, E>>,
    flow2: Flow<Result<T2, E>>,
    transform: suspend (T1, T2) -> R
): Flow<Result<R, E>> {
    return combine(flow, flow2) { r1, r2 ->
        binding {
            val s1 = r1.bind()
            val s2 = r2.bind()
            transform(s1, s2)
        }
    }
}

/**
 * Variant of Kotlin's [combine] that makes it easier to work with flows of [Result].
 *
 * Use this if [transform] might return an error. If your transform never returns an error, consider using [combineResult] instead.
 */
fun <T1, T2, E, R> combineResultWithErr(
    flow: Flow<Result<T1, E>>,
    flow2: Flow<Result<T2, E>>,
    transform: suspend (T1, T2) -> Result<R, E>
): Flow<Result<R, E>> {
    return combine(flow, flow2) { r1, r2 ->
        binding {
            val s1 = r1.bind()
            val s2 = r2.bind()
            transform(s1, s2).bind()
        }
    }
}

Variant of andThen that can be applied to a Flow<Result<V, E>>

/**
 * Apply [transform] on each element in this [Flow] if the element is an [Ok] otherwise return the
 *  error as-is into the flow.
 */
fun <V, E, U> Flow<Result<V, E>>.andThen(transform: suspend (V) -> Result<U, E>): Flow<Result<U, E>> {
    return map {
        when (it) {
            is Ok -> transform(it.value)
            is Err -> it
        }
    }
}

Variant of flatMapLatest that makes it simpler to work with Result<V, E>

/**
 * Like [andThen] but allows the [transform] to return a `Flow<Result>`. This method applies the
 *  [flatMapLatest] operator allowing you to return a flow from your transform
 */
fun <V, E, U> Flow<Result<V, E>>.andThenFlow(transform: suspend (V) -> Flow<Result<U, E>>): Flow<Result<U, E>> {
    return flatMapLatest {
        when (it) {
            is Ok -> transform(it.value)
            is Err -> flowOf(it)
        }
    }
}

As you can see, these utilities work, but they can be improved a lot. Specifically, they can benefit from the equivalent of binding {} (perhaps something like flowBinding {})

Artifact hosting

Would you consider hosting artifacts not on Jitpack? Jitpack is convenient, but also a convenient mechanism for malicious code injection: artifacts are built somewhere, by some computer neither you (the author) nor me (the consumer) have anything to do with. This is more of a "Trust us, it's fine, probably" model than I'd prefer for artifact delivery.

Hosting release artifacts is pretty straightforward with Bintray, which I (and anyone else with an interest in verifiable artifacts) would prefer.

getOrThrow?

This can be useful at boundaries, like a rest or graphql handler. I've added an extension function, but this might be useful in the core library. I did a quick search and didn't find any related issues.

fun <V, E> Result<V, E>.getOrThrow(transform : (E) -> Throwable): V = getOrElse { error -> throw transform(error) }

edit: right now I'm using all of these.

fun <V, E> Result<V, E>.getOrThrow(transform: (E) -> Throwable) = getOrElse { throw transform(it) }

fun <V: Any?> V.success() = Ok(this)

fun <E: Any?> E.failure() = Err(this)

inline fun <V: Any, reified E: Throwable> trying(noinline block: () -> V) = trying(E::class, block)

fun <V, E: Throwable> trying(klass: KClass<E>, block: () -> V): Result<V, E> = try {
    block().success()
} catch (e: Throwable) {
    @Suppress("UNCHECKED_CAST")
    when {
        klass.isInstance(e) -> e.failure() as Err<E>
        else -> throw e
    }
}

Struggle to run Gradle Check locally

I've tried to run gradlew check locally but I'm encountering some odd issue that doesn't seem to happen on CI:

https://scans.gradle.com/s/xz4bgfv3rbzty/console-log?task=:kotlin-result:linkDebugTestIosX64

:kotlin-result:linkDebugTestIosX64 FAILED
e: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld invocation reported errors
Please try to disable compiler caches and rerun the build. To disable compiler caches, add the following line to the gradle.properties file in the project's root directory:
    
    kotlin.native.cacheKind.iosX64=none
    
Also, consider filing an issue with full Gradle log here: https://kotl.in/issue
The /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld command returned non-zero exit code: 1.
output:
ld: warning: ignoring file /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/13.0.0/lib/darwin//libclang_rt.ios.a, missing required architecture x86_64 in file /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/13.0.0/lib/darwin//libclang_rt.ios.a (4 slices)
Undefined symbols for architecture x86_64:
  "___cpu_model", referenced from:
      polyHash_x86(int, unsigned short const*) in libstdlib-cache.a(result.o)
ld: symbol(s) not found for architecture x86_64

Could there be something I'm missing? I've tried opening the iOS emulator as I saw people with a similar error had seemed to work. Although I think native here is not necessarily targeting iOS.

Move benchmarking to separate sub project

I tried to add a benchmark test for suspendable binding but ended up with cyclical dependencies trying to have kotlin-result jvmBenchmark source set depend on kotlin-result-coroutine project (which already depends on kotlin-result).

Would make life a lot easier to just move benchmarking to its own subproject. It would also simplify the current kotlin-result gradle file which has a bunch of tricks in it already just to get the benchmarking plugin to play nice inside an mpp build.

This would also help delineate dependencies if we decided to benchmark against other implementations (arrow's Either, kotlin's own result type, kittinunf's result type).

Once completed we can look to move the work in #27 to there.

ensureNotNull-alike binding scope extensions

Arrow has a handy extension for eagerly returning from effect with an error if a value is null - ensureNotNull. Would be very helpful to have a similar extension here, right now need to rely on fairly nested flatMap:

val value = maybeGetValue()
  .flatMap {
    when (it) {
      null -> Err(ValueMissingError)
      else -> Ok(it)
    }
  }
  .bind()

vs

val value = maybeGetValue().bind()
ensureNotNull(value) { ValueMissingError }

Problem with using kotlin-result via maven

Hello

I am trying to add kotlin-result to my maven project, however strange things starts to happen then. After adding

<dependency>
  <groupId>com.michael-bull.kotlin-result</groupId>
  <artifactId>kotlin-result</artifactId>
  <version>1.1.11</version>
</dependency>

Dependency is added, jar is downloaded , however I cannot import any class - maven just doesn't see classes from com.github.michaelbull package.

My properties for kotlin and java are:

<java.version>11</java.version>
<kotlin.version>1.4.31</kotlin.version>

Do you know what can cause such problems? I have never had such problem with any other library.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.