diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Either.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Either.kt index 417b384acc4..69cb2d01ba8 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Either.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Either.kt @@ -821,12 +821,54 @@ public sealed class Either { * Example: * ``` * Right(12).mapLeft { "flower" } // Result: Right(12) - * Left(12).mapLeft { "flower" } // Result: Left("flower) + * Left(12).mapLeft { "flower" } // Result: Left("flower") * ``` */ public inline fun mapLeft(f: (A) -> C): Either = fold({ Left(f(it)) }, { Right(it) }) + /** + * The given function is applied as a fire and forget effect + * if this is a `Left`. + * When applied the result is ignored and the original + * Either value is returned + * + * Example: + * ``` + * Right(12).tapLeft { println("flower") } // Result: Right(12) + * Left(12).tapLeft { println("flower") } // Result: prints "flower" and returns: Left(12) + * ``` + */ + public inline fun tapLeft(f: (A) -> Unit): Either = + when (this) { + is Left -> { + f(this.value) + this + } + is Right -> this + } + + /** + * The given function is applied as a fire and forget effect + * if this is a `Right`. + * When applied the result is ignored and the original + * Either value is returned + * + * Example: + * ``` + * Right(12).tap { println("flower") } // Result: prints "flower" and returns: Right(12) + * Left(12).tap { println("flower") } // Result: Left(12) + * ``` + */ + public inline fun tap(f: (B) -> Unit): Either = + when (this) { + is Left -> this + is Right -> { + f(this.value) + this + } + } + /** * Map over Left and Right of this Either */ diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Option.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Option.kt index 65ada46e083..c26a0946d13 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Option.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Option.kt @@ -411,6 +411,48 @@ public sealed class Option { Some.unit ) { b, c, d, _, _, _, _, _, _, _ -> map(b, c, d) } + /** + * The given function is applied as a fire and forget effect + * if this is a `None`. + * When applied the result is ignored and the original + * None value is returned + * + * Example: + * ``` + * Some(12).tapNone { println("flower") } // Result: Some(12) + * none().tapNone { println("flower") } // Result: prints "flower" and returns: None + * ``` + */ + public inline fun tapNone(f: () -> Unit): Option = + when (this) { + is None -> { + f() + this + } + is Some -> this + } + + /** + * The given function is applied as a fire and forget effect + * if this is a `some`. + * When applied the result is ignored and the original + * Some value is returned + * + * Example: + * ``` + * Some(12).tap { println("flower") } // Result: prints "flower" and returns: Some(12) + * none().tap { println("flower") } // Result: None + * ``` + */ + public inline fun tap(f: (A) -> Unit): Option = + when (this) { + is None -> this + is Some -> { + f(this.value) + this + } + } + public inline fun zip( b: Option, c: Option, diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Validated.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Validated.kt index 4c00b2e8fdf..b92be209c8c 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Validated.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Validated.kt @@ -703,6 +703,48 @@ public sealed class Validated { public inline fun mapLeft(f: (E) -> EE): Validated = bimap(f, ::identity) + /** + * The given function is applied as a fire and forget effect + * if this is `Invalid`. + * When applied the result is ignored and the original + * Validated value is returned + * + * Example: + * ``` + * Valid(12).tapInvalid { println("flower") } // Result: Valid(12) + * Invalid(12).tapInvalid { println("flower") } // Result: prints "flower" and returns: Invalid(12) + * ``` + */ + public inline fun tapInvalid(f: (E) -> Unit): Validated = + when (this) { + is Invalid -> { + f(this.value) + this + } + is Valid -> this + } + + /** + * The given function is applied as a fire and forget effect + * if this is `Valid`. + * When applied the result is ignored and the original + * Validated value is returned + * + * Example: + * ``` + * Valid(12).tap { println("flower") } // Result: prints "flower" and returns: Valid(12) + * Invalid(12).tap { println("flower") } // Result: Invalid(12) + * ``` + */ + public inline fun tap(f: (A) -> Unit): Validated = + when (this) { + is Invalid -> this + is Valid -> { + f(this.value) + this + } + } + /** * apply the given function to the value with the given B when * valid, otherwise return the given B diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/EitherTest.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/EitherTest.kt index 4d1e1744258..846d1b1492b 100644 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/EitherTest.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/EitherTest.kt @@ -16,16 +16,17 @@ import arrow.core.test.generators.suspendFunThatThrows import arrow.core.test.laws.FxLaws import arrow.core.test.laws.MonoidLaws import arrow.typeclasses.Monoid -import io.kotest.property.Arb -import io.kotest.property.checkAll -import io.kotest.matchers.shouldBe import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.kotest.property.Arb import io.kotest.property.arbitrary.choice import io.kotest.property.arbitrary.constant import io.kotest.property.arbitrary.int +import io.kotest.property.arbitrary.long import io.kotest.property.arbitrary.map import io.kotest.property.arbitrary.negativeInts import io.kotest.property.arbitrary.string +import io.kotest.property.checkAll class EitherTest : UnitSpec() { @@ -64,6 +65,32 @@ class EitherTest : UnitSpec() { } } + "tap applies effects returning the original value" { + checkAll(Arb.either(Arb.long(), Arb.int())) { either -> + var effect = 0 + val res = either.tap { effect += 1 } + val expected = when (either) { + is Left -> 0 + is Right -> 1 + } + effect shouldBe expected + res shouldBe either + } + } + + "tapLeft applies effects returning the original value" { + checkAll(Arb.either(Arb.long(), Arb.int())) { either -> + var effect = 0 + val res = either.tapLeft { effect += 1 } + val expected = when (either) { + is Left -> 1 + is Right -> 0 + } + effect shouldBe expected + res shouldBe either + } + } + "fold should apply first op if Left and second op if Right" { checkAll(Arb.intSmall(), Arb.intSmall()) { a, b -> val right: Either = Right(a) diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/OptionTest.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/OptionTest.kt index 752c34497b8..e5d2524b417 100755 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/OptionTest.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/OptionTest.kt @@ -11,13 +11,14 @@ import arrow.core.test.laws.MonoidLaws import arrow.typeclasses.Monoid import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe -import io.kotest.property.checkAll import io.kotest.property.Arb import io.kotest.property.arbitrary.bool -import io.kotest.property.arbitrary.map import io.kotest.property.arbitrary.int +import io.kotest.property.arbitrary.long +import io.kotest.property.arbitrary.map import io.kotest.property.arbitrary.orNull import io.kotest.property.arbitrary.string +import io.kotest.property.checkAll class OptionTest : UnitSpec() { @@ -75,6 +76,32 @@ class OptionTest : UnitSpec() { } shouldBe None } + "tap applies effects returning the original value" { + checkAll(Arb.option(Arb.long())) { option -> + var effect = 0 + val res = option.tap { effect += 1 } + val expected = when (option) { + is Some -> 1 + is None -> 0 + } + effect shouldBe expected + res shouldBe option + } + } + + "tapNone applies effects returning the original value" { + checkAll(Arb.option(Arb.long())) { option -> + var effect = 0 + val res = option.tapNone { effect += 1 } + val expected = when (option) { + is Some -> 0 + is None -> 1 + } + effect shouldBe expected + res shouldBe option + } + } + "fromNullable should work for both null and non-null values of nullable types" { checkAll { a: Int? -> // This seems to be generating only non-null values, so it is complemented by the next test diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/ValidatedTest.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/ValidatedTest.kt index 2769e75e383..1a4ad8f8054 100644 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/ValidatedTest.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/ValidatedTest.kt @@ -136,6 +136,32 @@ class ValidatedTest : UnitSpec() { } } + "tap applies effects returning the original value" { + checkAll(Arb.validated(Arb.long(), Arb.int())) { validated -> + var effect = 0 + val res = validated.tap { effect += 1 } + val expected = when (validated) { + is Validated.Valid -> 1 + is Validated.Invalid -> 0 + } + effect shouldBe expected + res shouldBe validated + } + } + + "tapInvalid applies effects returning the original value" { + checkAll(Arb.validated(Arb.long(), Arb.int())) { validated -> + var effect = 0 + val res = validated.tapInvalid { effect += 1 } + val expected = when (validated) { + is Validated.Valid -> 0 + is Validated.Invalid -> 1 + } + effect shouldBe expected + res shouldBe validated + } + } + "zip is derived from flatMap" { checkAll( Arb.validated(Arb.long().orNull(), Arb.int().orNull()),