diff --git a/lib/api/lib.api b/lib/api/lib.api index 7e43d38..65bcaf4 100644 --- a/lib/api/lib.api +++ b/lib/api/lib.api @@ -236,8 +236,11 @@ public final class app/cash/quiver/extensions/OptionKt { public static final fun forEach (Larrow/core/Option;Lkotlin/jvm/functions/Function1;)V public static final fun ifAbsent (Larrow/core/Option;Lkotlin/jvm/functions/Function0;)V public static final fun or (Larrow/core/Option;Larrow/core/Option;)Larrow/core/Option; + public static final fun orEmpty (Larrow/core/Option;Lkotlin/jvm/functions/Function1;)Ljava/lang/String; public static final fun toValidatedNel (Larrow/core/Option;Lkotlin/jvm/functions/Function0;)Larrow/core/Validated; + public static final fun traverseOp (Larrow/core/Option;Lkotlin/jvm/functions/Function1;)Larrow/core/Either; public static final fun unit (Larrow/core/Option;)Larrow/core/Option; + public static final fun zipOp (Larrow/core/Option;Larrow/core/Option;Lkotlin/jvm/functions/Function2;)Larrow/core/Option; } public final class app/cash/quiver/extensions/ResultKt { diff --git a/lib/src/main/kotlin/app/cash/quiver/extensions/Option.kt b/lib/src/main/kotlin/app/cash/quiver/extensions/Option.kt index fb278cf..cf1c173 100644 --- a/lib/src/main/kotlin/app/cash/quiver/extensions/Option.kt +++ b/lib/src/main/kotlin/app/cash/quiver/extensions/Option.kt @@ -2,11 +2,15 @@ package app.cash.quiver.extensions +import arrow.core.Either import arrow.core.None import arrow.core.Option import arrow.core.Some import arrow.core.ValidatedNel +import arrow.core.getOrElse import arrow.core.nonEmptyListOf +import arrow.core.raise.option +import arrow.core.right /** * Takes a function to run if your Option is None. Returns Unit if your Option is Some. @@ -41,3 +45,25 @@ infix fun Option.or(other: Option): Option = when (this) { is Some -> this is None -> other } + +/** + * Given a function that returns an Either, will turn your Option of A into an Option of B in the context of Either, + * where the Left value will always be the None. + */ +inline fun Option.traverseOp(fa: (A) -> Either): Either> = fold( + { None.right() }, + { fa(it).map(::Some) } +) + +/** + * Given an optional value A and B, will return you an optional C + */ +inline fun Option.zipOp(b: Option, f: (A, B) -> C): Option = option { + f(bind(), b.bind()) +} + +/** + * Will return an empty string if the Option supplied is None + */ +fun Option.orEmpty(f: (T) -> String): String = this.map(f).getOrElse { "" } + diff --git a/lib/src/test/kotlin/app/cash/quiver/extensions/OptionTest.kt b/lib/src/test/kotlin/app/cash/quiver/extensions/OptionTest.kt index e57d11e..cf83865 100644 --- a/lib/src/test/kotlin/app/cash/quiver/extensions/OptionTest.kt +++ b/lib/src/test/kotlin/app/cash/quiver/extensions/OptionTest.kt @@ -1,7 +1,10 @@ package app.cash.quiver.extensions import arrow.core.None +import arrow.core.right import arrow.core.some +import io.kotest.assertions.arrow.core.shouldBeRight +import io.kotest.assertions.arrow.core.shouldBeSome import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe import io.kotest.property.Arb @@ -29,4 +32,24 @@ class OptionTest : StringSpec({ None.or(other) shouldBe other } } + + "traverse returns an either of option" { + "apple".some().traverseOp { + it.length.right() + } shouldBeRight (5.some()) + } + + "zip returns an optional combination of optional values" { + 5.some().zipOp("apple".some()) { a: Int, b: String -> + b.length * a + } shouldBeSome 25 + } + + "orEmpty returns an empty string if used on a None" { + None.orEmpty { "I am an useless string " } shouldBe "" + } + + "orEmpty returns the string supplied if value is Some" { + "apple".some().orEmpty { "I wanna eat $it" } shouldBe "I wanna eat apple" + } })