Skip to content

Commit

Permalink
Rework IorRaise impl to use EmptyValue, and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kyay10 committed Dec 24, 2023
1 parent 731a9b2 commit de229bc
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 61 deletions.
9 changes: 5 additions & 4 deletions arrow-libs/core/arrow-core/api/arrow-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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 <init> (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 <init> (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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ public inline fun <A> identity(a: A): A = a
*/
@PublishedApi
internal object EmptyValue {
@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
public inline fun <A> unbox(value: Any?): A =
if (value === this) null as A else value as A
@Suppress("UNCHECKED_CAST")
inline fun <A> unbox(value: Any?): A =
fold(value, { null as A }, ::identity)

public inline fun <T> combine(first: Any?, second: T, combine: (T, T) -> T): T =
if (first === EmptyValue) second else combine(first as T, second)
inline fun <T> combine(first: Any?, second: T, combine: (T, T) -> T): T =
fold(first, { second }, { t: T -> combine(t, second) })

@Suppress("UNCHECKED_CAST")
inline fun <T, R> fold(value: Any?, ifEmpty: () -> R, ifNotEmpty: (T) -> R): R =
if (value === EmptyValue) ifEmpty() else ifNotEmpty(value as T)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -88,16 +79,20 @@ public inline fun <A> option(block: OptionRaise.() -> A): Option<A> =
* 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 <Error, A> ior(noinline combineError: (Error, Error) -> Error, @BuilderInference block: IorRaise<Error>.() -> A): Ior<Error, A> {
val state: Atomic<Option<Error>> = Atomic(None)
return fold<Error, A, Ior<Error, 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 <Error, A> ior(crossinline combineError: (Error, Error) -> Error, @BuilderInference block: IorRaise<Error>.() -> A): Ior<Error, A> {
val state: Atomic<Any?> = 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 <Error> Raise<Error>.IorRaise(crossinline addError: (Error) -> Unit): IorRaise<Error> =
object: IorRaise<Error>(this) {
override fun addError(e: Error) = addError(e)
}

/**
* Run a computation [block] using [Raise]. and return its outcome as [IorNel].
* - [Ior.Right] represents success,
Expand All @@ -114,15 +109,8 @@ public inline fun <Error, A> 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 <Error, A> iorNel(noinline combineError: (NonEmptyList<Error>, NonEmptyList<Error>) -> NonEmptyList<Error> = { a, b -> a + b }, @BuilderInference block: IorRaise<NonEmptyList<Error>>.() -> A): IorNel<Error, A> {
val state: Atomic<Option<NonEmptyList<Error>>> = Atomic(None)
return fold<NonEmptyList<Error>, A, Ior<NonEmptyList<Error>, 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 <Error, A> iorNel(noinline combineError: (NonEmptyList<Error>, NonEmptyList<Error>) -> NonEmptyList<Error> = { a, b -> a + b }, @BuilderInference block: IorRaise<NonEmptyList<Error>>.() -> A): IorNel<Error, A> =
ior(combineError, block)

/**
* Implementation of [Raise] used by `ignoreErrors`.
Expand Down Expand Up @@ -304,14 +292,11 @@ public class OptionRaise(private val raise: Raise<None>) : Raise<None> by raise
* Implementation of [Raise] used by [ior].
* You should never use this directly.
*/
public class IorRaise<Error> @PublishedApi internal constructor(
@PublishedApi internal val combineError: (Error, Error) -> Error,
private val state: Atomic<Option<Error>>,
public abstract class IorRaise<Error> @PublishedApi internal constructor(
private val raise: Raise<Error>,
) : Raise<Error> {

@RaiseDSL
override fun raise(r: Error): Nothing = raise.raise(combine(r))
) : Raise<Error> by raise {
@PublishedApi
internal abstract fun addError(e: Error)

@RaiseDSL
@JvmName("bindAllIor")
Expand All @@ -334,7 +319,7 @@ public class IorRaise<Error> @PublishedApi internal constructor(
is Ior.Left -> raise(value)
is Ior.Right -> value
is Ior.Both -> {
combine(leftValue)
addError(leftValue)
rightValue
}
}
Expand All @@ -343,23 +328,9 @@ public class IorRaise<Error> @PublishedApi internal constructor(
public fun <K, V> Map<K, Ior<Error, V>>.bindAll(): Map<K, V> =
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 <A> recover(
@BuilderInference block: IorRaise<Error>.() -> 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<Error, A>({ block(IorRaise(::addError)) }, recover)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})

0 comments on commit de229bc

Please sign in to comment.