diff --git a/arrow-libs/core/arrow-core/api/arrow-core.api b/arrow-libs/core/arrow-core/api/arrow-core.api index 6f0e5cd2e06..335bb377453 100644 --- a/arrow-libs/core/arrow-core/api/arrow-core.api +++ b/arrow-libs/core/arrow-core/api/arrow-core.api @@ -1040,6 +1040,7 @@ public final class arrow/core/raise/RaiseKt { public static final fun _foldOrThrow (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun _foldUnsafe (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun _merge (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static final fun attempt (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun catch (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun catch (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function1; public static final fun catch (Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function2; @@ -1089,6 +1090,7 @@ public final class arrow/core/raise/RaiseKt { public static final fun toResult (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun toResult (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun traced (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; + public static final fun valueOrEmpty (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun withError (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun zipOrAccumulate (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function9;)Ljava/lang/Object; public static final fun zipOrAccumulate (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function8;)Ljava/lang/Object; diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Raise.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Raise.kt index aa01e7663d8..862e714853c 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Raise.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Raise.kt @@ -14,7 +14,6 @@ import arrow.core.recover import kotlin.coroutines.cancellation.CancellationException import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind.AT_MOST_ONCE -import kotlin.contracts.InvocationKind.EXACTLY_ONCE import kotlin.contracts.contract import kotlin.experimental.ExperimentalTypeInference import kotlin.jvm.JvmMultifileClass @@ -662,7 +661,8 @@ public inline fun Raise.withError( contract { callsInPlace(transform, AT_MOST_ONCE) } - return recover(block) { raise(transform(it)) } + val error = attempt { return block(this) } + raise(transform(error)) } /** @@ -693,3 +693,41 @@ public inline fun merge( } return recover(block, ::identity) } + +/** + * Execute the [Raise] context function resulting in an early-return to an outer scope, + * or any _logical error_ of type [Error]. + * This function behaves like an imperative version of recover. + * + * ```kotlin + * val foo = either { if (Random.nextBoolean()) raise("failed") else 42 } + * + * fun test() { + * either { + * val msg = attempt { return@either foo.bind() } + * raise(msg.toList()) + * } shouldBe foo.mapLeft(String::toList) + * + * run { + * attempt { return@run foo.bind() } + * 1 + * } shouldBe foo.getOrElse { 1 } + * } + * + * ``` + * + * + */ +@RaiseDSL +public inline fun attempt(block: Raise.() -> Nothing): Error { + contract { + callsInPlace(block, AT_MOST_ONCE) + } + return merge(block) +} diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/RaiseAccumulate.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/RaiseAccumulate.kt index 6e7fae7c370..97d9d2372e8 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/RaiseAccumulate.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/RaiseAccumulate.kt @@ -256,17 +256,17 @@ public inline fun Raise.zipOrAccumu ): J { contract { callsInPlace(block, AT_MOST_ONCE) } var error: Any? = EmptyValue - val a = recover({ action1(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue } - val b = recover({ action2(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue } - val c = recover({ action3(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue } - val d = recover({ action4(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue } - val e = recover({ action5(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue } - val f = recover({ action6(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue } - val g = recover({ action7(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue } - val h = recover({ action8(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue } - val i = recover({ action9(RaiseAccumulate(this)) }) { error = combine(error, it.reduce(combine), combine); EmptyValue } - return if (error !== EmptyValue) raise(unbox(error)) - else block(unbox(a), unbox(b), unbox(c), unbox(d), unbox(e), unbox(f), unbox(g), unbox(h), unbox(i)) + val a = valueOrEmpty(action1) { error = combine(error, it.reduce(combine), combine) } + val b = valueOrEmpty(action2) { error = combine(error, it.reduce(combine), combine) } + val c = valueOrEmpty(action3) { error = combine(error, it.reduce(combine), combine) } + val d = valueOrEmpty(action4) { error = combine(error, it.reduce(combine), combine) } + val e = valueOrEmpty(action5) { error = combine(error, it.reduce(combine), combine) } + val f = valueOrEmpty(action6) { error = combine(error, it.reduce(combine), combine) } + val g = valueOrEmpty(action7) { error = combine(error, it.reduce(combine), combine) } + val h = valueOrEmpty(action8) { error = combine(error, it.reduce(combine), combine) } + val i = valueOrEmpty(action9) { error = combine(error, it.reduce(combine), combine) } + if (error !== EmptyValue) raise(unbox(error)) + return block(unbox(a), unbox(b), unbox(c), unbox(d), unbox(e), unbox(f), unbox(g), unbox(h), unbox(i)) } /** @@ -487,19 +487,33 @@ public inline fun Raise = mutableListOf() - val a = recover({ action1(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue } - val b = recover({ action2(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue } - val c = recover({ action3(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue } - val d = recover({ action4(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue } - val e = recover({ action5(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue } - val f = recover({ action6(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue } - val g = recover({ action7(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue } - val h = recover({ action8(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue } - val i = recover({ action9(RaiseAccumulate(this)) }) { error.addAll(it); EmptyValue } + val a = valueOrEmpty(action1, error::addAll) + val b = valueOrEmpty(action2, error::addAll) + val c = valueOrEmpty(action3, error::addAll) + val d = valueOrEmpty(action4, error::addAll) + val e = valueOrEmpty(action5, error::addAll) + val f = valueOrEmpty(action6, error::addAll) + val g = valueOrEmpty(action7, error::addAll) + val h = valueOrEmpty(action8, error::addAll) + val i = valueOrEmpty(action9, error::addAll) error.toNonEmptyListOrNull()?.let { raise(it) } return block(unbox(a), unbox(b), unbox(c), unbox(d), unbox(e), unbox(f), unbox(g), unbox(h), unbox(i)) } +@PublishedApi +internal inline fun valueOrEmpty( + block: RaiseAccumulate.() -> A, + addErrors: (NonEmptyList) -> Unit +): Any? { + contract { + callsInPlace(block, AT_MOST_ONCE) + callsInPlace(addErrors, AT_MOST_ONCE) + } + val errorList = attempt { return block(RaiseAccumulate(this)) } + addErrors(errorList) + return EmptyValue +} + /** * Transform every element of [iterable] using the given [transform], or accumulate all the occurred errors using [combine]. * diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/EagerEffectSpec.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/EagerEffectSpec.kt index b4954a66ae3..a6af49c90d6 100644 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/EagerEffectSpec.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/EagerEffectSpec.kt @@ -293,4 +293,25 @@ class EagerEffectSpec { .get() shouldBe i } } + + @Test fun attemptSuccess() = runTest { + checkAll(Arb.int()) { i -> + run { attempt { return@run i } } shouldBe i + } + } + + @Test fun attemptRaise() = runTest { + checkAll(Arb.int(), Arb.string()) { i, s -> + run { + attempt { raise(i) } shouldBe i + s + } shouldBe s + } + } + + @Test fun attemptNested() = runTest { + checkAll(Arb.int()) { i -> + attempt { attempt { raise(i) } } shouldBe i + } + } } diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-raise-dsl-13.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-raise-dsl-13.kt new file mode 100644 index 00000000000..53a48a558f8 --- /dev/null +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/example-raise-dsl-13.kt @@ -0,0 +1,23 @@ +// This file was automatically generated from Raise.kt by Knit tool. Do not edit. +package arrow.core.examples.exampleRaiseDsl13 + +import arrow.core.getOrElse +import arrow.core.raise.attempt +import arrow.core.raise.either +import io.kotest.matchers.shouldBe +import kotlin.random.Random + +val foo = either { if (Random.nextBoolean()) raise("failed") else 42 } + +fun test() { + either { + val msg = attempt { return@either foo.bind() } + raise(msg.toList()) + } shouldBe foo.mapLeft(String::toList) + + run { + attempt { return@run foo.bind() } + 1 + } shouldBe foo.getOrElse { 1 } +} + diff --git a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/test/RaiseKnitTest.kt b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/test/RaiseKnitTest.kt index f1710a181f9..1f49117674e 100644 --- a/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/test/RaiseKnitTest.kt +++ b/arrow-libs/core/arrow-core/src/jvmTest/kotlin/examples/test/RaiseKnitTest.kt @@ -49,4 +49,8 @@ class RaiseKnitTest { arrow.core.examples.exampleRaiseDsl12.test() } + @Test fun exampleRaiseDsl13() = runTest { + arrow.core.examples.exampleRaiseDsl13.test() + } + }