Skip to content

Commit

Permalink
Add iorNel builder and Ior.toIorNel function
Browse files Browse the repository at this point in the history
  • Loading branch information
sampengilly committed Aug 16, 2023
1 parent cb03363 commit 78be512
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1165,6 +1165,14 @@ public fun <A, B, C> Ior<A, Validated<B, C>>.sequence(): Validated<B, Ior<A, C>>
{ b -> b.map { Right(it) } },
{ a, b -> b.map { Both(a, it) } })

/**
* Given an [Ior] with an error type [A], returns an [IorNel] with the same
* error type. Wraps the original error in a [NonEmptyList] so that it can be
* combined with an [IorNel] in a Raise DSL which operates on one.
*/
public fun <A, B> Ior<A, B>.toIorNel(): IorNel<A, B> =
mapLeft { it.nel() }

/**
* Given [B] is a sub type of [C], re-type this value from Ior<A, B> to Ior<A, B>
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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
Expand Down Expand Up @@ -94,6 +95,32 @@ public inline fun <Error, A> ior(noinline combineError: (Error, Error) -> Error,
)
}

/**
* Run a computation [block] using [Raise]. and return its outcome as [IorNel].
* - [Ior.Right] represents success,
* - [Ior.Left] represents logical failure which made it impossible to continue,
* - [Ior.Both] represents that some logical failures were raised,
* but it was possible to continue until producing a final value.
*
* This function re-throws any exceptions thrown within the [Raise] block.
*
* In both [Ior.Left] and [Ior.Both] cases, if more than one logical failure
* has been raised, they are combined using [combineError]. This defaults to
* combining [NonEmptyList]s by concatenating them.
*
* 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) }) }
)
}

/**
* Implementation of [Raise] used by `ignoreErrors`.
* You should never use this directly.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package arrow.core.continuations

import arrow.core.Either
import arrow.core.Ior
import arrow.core.toIorNel
import arrow.core.raise.iorNel
import arrow.core.test.nonEmptyList
import arrow.typeclasses.Semigroup
import io.kotest.core.spec.style.StringSpec
Expand Down Expand Up @@ -57,4 +59,12 @@ class IorSpec :
one + two
} shouldBe Ior.Left("Hello, World!")
}

"iorNel accumulates using Nel" {
iorNel {
val one = Ior.Both("ErrorOne", 1).toIorNel().bind()
val two = Ior.Both("ErrorTwo", 2).toIorNel().bind()
one + two
} shouldBe Ior.Both(listOf("ErrorOne", "ErrorTwo"), 3)
}
})

0 comments on commit 78be512

Please sign in to comment.