diff --git a/CHANGELOG.md b/CHANGELOG.md index 31cddd7..7267d60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added * Adds `Result.unit(): Result` as alias for `.map { }` (Jem Mawson) +* Adds `Result.tap` and `Result.flatTap` (Jem Mawson) ## [0.5.5] - 2024-06-20 diff --git a/lib/api/lib.api b/lib/api/lib.api index 4a9dc4b..bd9ba97 100644 --- a/lib/api/lib.api +++ b/lib/api/lib.api @@ -1,5 +1,8 @@ public final class app/cash/quiver/Absent : app/cash/quiver/Outcome { public static final field INSTANCE Lapp/cash/quiver/Absent; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } public final class app/cash/quiver/Failure : app/cash/quiver/Outcome { @@ -272,10 +275,12 @@ public final class app/cash/quiver/extensions/OptionKt { public final class app/cash/quiver/extensions/ResultKt { public static final fun failure (Ljava/lang/Throwable;)Ljava/lang/Object; + public static final fun flatTap (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun flatten (Ljava/lang/Object;)Ljava/lang/Object; public static final fun mapFailure (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun orThrow (Ljava/lang/Object;)Ljava/lang/Object; public static final fun success (Ljava/lang/Object;)Ljava/lang/Object; + public static final fun tap (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun toEither (Ljava/lang/Object;)Larrow/core/Either; public static final fun toResult (Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; public static final fun tryCatch (Lkotlin/Result$Companion;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; diff --git a/lib/src/main/kotlin/app/cash/quiver/Outcome.kt b/lib/src/main/kotlin/app/cash/quiver/Outcome.kt index 2762ef6..ca39c68 100644 --- a/lib/src/main/kotlin/app/cash/quiver/Outcome.kt +++ b/lib/src/main/kotlin/app/cash/quiver/Outcome.kt @@ -108,7 +108,7 @@ sealed class Outcome constructor(val inner: Either>) */ data class Present(val value: A) : Outcome(value.some().right()) data class Failure(val error: E) : Outcome(error.left()) -object Absent : Outcome(None.right()) +data object Absent : Outcome(None.right()) fun A.present(): Outcome = Present(this) fun E.failure(): Outcome = Failure(this) diff --git a/lib/src/main/kotlin/app/cash/quiver/extensions/Result.kt b/lib/src/main/kotlin/app/cash/quiver/extensions/Result.kt index 2ae56dc..5734e23 100644 --- a/lib/src/main/kotlin/app/cash/quiver/extensions/Result.kt +++ b/lib/src/main/kotlin/app/cash/quiver/extensions/Result.kt @@ -63,3 +63,20 @@ fun Result>.flatten(): Result = flatMap(::identity) * Map success to Unit, included for consistency with Either. */ fun Result.unit() = map { } + +/** + * Performs an effect over successes but maps the original value back into + * the Result. + */ +inline fun Result.tap(f: (A) -> B): Result = this.map { a -> + f(a) + a +} + +/** + * Performs an effect over successes but maps the original value back into + * the Result. This is useful for mixing with validation functions. + */ +inline fun Result.flatTap(f: (A) -> Result): Result = this.flatMap { a -> + f(a).map { a } +} diff --git a/lib/src/test/kotlin/app/cash/quiver/extensions/ResultTest.kt b/lib/src/test/kotlin/app/cash/quiver/extensions/ResultTest.kt index c93dbb9..845aae0 100644 --- a/lib/src/test/kotlin/app/cash/quiver/extensions/ResultTest.kt +++ b/lib/src/test/kotlin/app/cash/quiver/extensions/ResultTest.kt @@ -1,5 +1,6 @@ package app.cash.quiver.extensions +import arrow.core.raise.result import io.kotest.assertions.arrow.core.shouldBeLeft import io.kotest.assertions.arrow.core.shouldBeRight import io.kotest.assertions.throwables.shouldThrow @@ -79,4 +80,24 @@ class ResultTest : StringSpec({ e.failure().unit() shouldBe e.failure() } + "tap performs computation but keeps original value" { + val right: Result = "hello".success() + var sideEffect: String? = null + + right.tap { a -> sideEffect = "$a world" }.shouldBeSuccess("hello") + sideEffect shouldBe "hello world" + } + + "flatTap performs computation but keeps original value" { + val e = RuntimeException("banana") + val right: Result = "hello".success() + var sideEffect: String? = null + + right.flatTap { a -> result { sideEffect = "$a world" } }.shouldBeSuccess("hello") + sideEffect shouldBe "hello world" + + right.flatTap { Result.failure(e) }.shouldBeFailure(e) + e.failure().flatTap { Result.failure(Exception("broken")) }.shouldBeFailure(e) + } + })