diff --git a/arrow-libs/core/arrow-core/api/arrow-core.api b/arrow-libs/core/arrow-core/api/arrow-core.api index 0a7d5b8a633..eff13529662 100644 --- a/arrow-libs/core/arrow-core/api/arrow-core.api +++ b/arrow-libs/core/arrow-core/api/arrow-core.api @@ -374,6 +374,7 @@ public final class arrow/core/EitherKt { public final class arrow/core/EmptyValue { public static final field INSTANCE Larrow/core/EmptyValue; public final fun combine (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; + public final fun fold (Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public final fun unbox (Ljava/lang/Object;)Ljava/lang/Object; } @@ -3422,8 +3423,9 @@ public final class arrow/core/raise/IgnoreErrorsRaise : arrow/core/raise/Raise { public fun shift (Ljava/lang/Object;)Ljava/lang/Object; } -public final class arrow/core/raise/IorRaise : arrow/core/raise/Raise { - public fun (Lkotlin/jvm/functions/Function2;Ljava/util/concurrent/atomic/AtomicReference;Larrow/core/raise/Raise;)V +public abstract class arrow/core/raise/IorRaise : arrow/core/raise/Raise { + public fun (Larrow/core/raise/Raise;)V + public abstract fun addError (Ljava/lang/Object;)V public fun attempt (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun bind (Larrow/core/Either;)Ljava/lang/Object; public final fun bind (Larrow/core/Ior;)Ljava/lang/Object; @@ -3441,8 +3443,6 @@ public final class arrow/core/raise/IorRaise : arrow/core/raise/Raise { public final fun bindAllIor (Ljava/util/Map;)Ljava/util/Map; public final fun bindAllIor (Ljava/util/Set;)Ljava/util/Set; public fun catch (Larrow/core/continuations/Effect;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun combine (Ljava/lang/Object;)Ljava/lang/Object; - public final fun getCombineError ()Lkotlin/jvm/functions/Function2; public fun invoke (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public fun invoke (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun raise (Ljava/lang/Object;)Ljava/lang/Void; @@ -3582,6 +3582,7 @@ public abstract interface annotation class arrow/core/raise/RaiseDSL : java/lang } public final class arrow/core/raise/RaiseKt { + public static final fun IorRaise (Larrow/core/raise/Raise;Lkotlin/jvm/functions/Function1;)Larrow/core/raise/IorRaise; public static final fun _fold (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; 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; diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/predef.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/predef.kt index 65526bea9fc..2d1934b11ec 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/predef.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/predef.kt @@ -13,12 +13,16 @@ public inline fun identity(a: A): A = a */ @PublishedApi internal object EmptyValue { - @Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE") - public inline fun unbox(value: Any?): A = - if (value === this) null as A else value as A + @Suppress("UNCHECKED_CAST") + inline fun unbox(value: Any?): A = + fold(value, { null as A }, ::identity) - public inline fun combine(first: Any?, second: T, combine: (T, T) -> T): T = - if (first === EmptyValue) second else combine(first as T, second) + inline fun combine(first: Any?, second: T, combine: (T, T) -> T): T = + fold(first, { second }, { t: T -> combine(t, second) }) + + @Suppress("UNCHECKED_CAST") + inline fun fold(value: Any?, ifEmpty: () -> R, ifNotEmpty: (T) -> R): R = + if (value === EmptyValue) ifEmpty() else ifNotEmpty(value as T) } /** diff --git a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Builders.kt b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Builders.kt index 362ec819819..19d8eab43c4 100644 --- a/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Builders.kt +++ b/arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/raise/Builders.kt @@ -5,17 +5,8 @@ package arrow.core.raise import arrow.atomic.Atomic -import arrow.atomic.updateAndGet -import arrow.core.Either -import arrow.core.Ior -import arrow.core.IorNel -import arrow.core.NonEmptyList -import arrow.core.NonEmptySet -import arrow.core.None -import arrow.core.Option -import arrow.core.Some -import arrow.core.getOrElse -import arrow.core.identity +import arrow.atomic.update +import arrow.core.* import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract import kotlin.experimental.ExperimentalTypeInference @@ -88,16 +79,20 @@ public inline fun option(block: OptionRaise.() -> A): Option = * Read more about running a [Raise] computation in the * [Arrow docs](https://arrow-kt.io/learn/typed-errors/working-with-typed-errors/#running-and-inspecting-results). */ -public inline fun ior(noinline combineError: (Error, Error) -> Error, @BuilderInference block: IorRaise.() -> A): Ior { - val state: Atomic> = Atomic(None) - return fold>( - { block(IorRaise(combineError, state, this)) }, - { e -> throw e }, - { e -> Ior.Left(state.get().getOrElse { e }) }, - { a -> state.get().fold({ Ior.Right(a) }, { Ior.Both(it, a) }) } +public inline fun ior(crossinline combineError: (Error, Error) -> Error, @BuilderInference block: IorRaise.() -> A): Ior { + val state: Atomic = Atomic(EmptyValue) + return fold( + { block(IorRaise { e -> state.update { EmptyValue.combine(it, e, combineError) } }) }, + { e -> Ior.Left(EmptyValue.combine(state.get(), e, combineError)) }, + { a -> EmptyValue.fold(state.get(), { Ior.Right(a) }, { e: Error -> Ior.Both(e, a) }) } ) } +@PublishedApi internal inline fun Raise.IorRaise(crossinline addError: (Error) -> Unit): IorRaise = + object: IorRaise(this) { + override fun addError(e: Error) = addError(e) + } + /** * Run a computation [block] using [Raise]. and return its outcome as [IorNel]. * - [Ior.Right] represents success, @@ -114,15 +109,8 @@ public inline fun ior(noinline combineError: (Error, Error) -> Error, * Read more about running a [Raise] computation in the * [Arrow docs](https://arrow-kt.io/learn/typed-errors/working-with-typed-errors/#running-and-inspecting-results). */ -public inline fun iorNel(noinline combineError: (NonEmptyList, NonEmptyList) -> NonEmptyList = { a, b -> a + b }, @BuilderInference block: IorRaise>.() -> A): IorNel { - val state: Atomic>> = Atomic(None) - return fold, A, Ior, A>>( - { block(IorRaise(combineError, state, this)) }, - { e -> throw e }, - { e -> Ior.Left(state.get().getOrElse { e }) }, - { a -> state.get().fold({ Ior.Right(a) }, { Ior.Both(it, a) }) } - ) -} +public inline fun iorNel(noinline combineError: (NonEmptyList, NonEmptyList) -> NonEmptyList = { a, b -> a + b }, @BuilderInference block: IorRaise>.() -> A): IorNel = + ior(combineError, block) /** * Implementation of [Raise] used by `ignoreErrors`. @@ -304,14 +292,11 @@ public class OptionRaise(private val raise: Raise) : Raise by raise * Implementation of [Raise] used by [ior]. * You should never use this directly. */ -public class IorRaise @PublishedApi internal constructor( - @PublishedApi internal val combineError: (Error, Error) -> Error, - private val state: Atomic>, +public abstract class IorRaise @PublishedApi internal constructor( private val raise: Raise, -) : Raise { - - @RaiseDSL - override fun raise(r: Error): Nothing = raise.raise(combine(r)) +) : Raise by raise { + @PublishedApi + internal abstract fun addError(e: Error) @RaiseDSL @JvmName("bindAllIor") @@ -334,7 +319,7 @@ public class IorRaise @PublishedApi internal constructor( is Ior.Left -> raise(value) is Ior.Right -> value is Ior.Both -> { - combine(leftValue) + addError(leftValue) rightValue } } @@ -343,23 +328,9 @@ public class IorRaise @PublishedApi internal constructor( public fun Map>.bindAll(): Map = mapValues { (_, v) -> v.bind() } - @PublishedApi - internal fun combine(other: Error): Error = - state.updateAndGet { prev -> - Some(prev.map { combineError(it, other) }.getOrElse { other }) - }.getOrElse { other } - @RaiseDSL public inline fun recover( @BuilderInference block: IorRaise.() -> A, recover: (error: Error) -> A, - ): A = when (val ior = ior(combineError, block)) { - is Ior.Both -> { - combine(ior.leftValue) - ior.rightValue - } - - is Ior.Left -> recover(ior.value) - is Ior.Right -> ior.value - } + ): A = recover({ block(IorRaise(::addError)) }, recover) } diff --git a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/IorSpec.kt b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/IorSpec.kt index 007135ee848..f0ed63bc844 100644 --- a/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/IorSpec.kt +++ b/arrow-libs/core/arrow-core/src/commonTest/kotlin/arrow/core/raise/IorSpec.kt @@ -78,10 +78,27 @@ class IorSpec : StringSpec({ "Recover works as expected" { ior(String::plus) { - val one = recover({ Ior.Left("Hello").bind() }) { 1 } + val one = recover({ + Ior.Both("Hi", Unit).bind() + Ior.Left("Hello").bind() + }) { 1 } val two = Ior.Right(2).bind() val three = Ior.Both(", World", 3).bind() one + two + three - } shouldBe Ior.Both(", World", 6) + } shouldBe Ior.Both("Hi, World", 6) + } + + "try catch can recover from raise" { + ior(String::plus) { + val one = try { + Ior.Both("Hi", Unit).bind() + Ior.Left("Hello").bind() + } catch (e: Throwable) { + 1 + } + val two = Ior.Right(2).bind() + val three = Ior.Both(", World", 3).bind() + one + two + three + } shouldBe Ior.Both("Hi, World", 6) } })