Skip to content

Commit

Permalink
Added mapAbort
Browse files Browse the repository at this point in the history
  • Loading branch information
johnhungerford committed Dec 5, 2024
1 parent d417f0d commit e23f858
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 26 deletions.
19 changes: 2 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3416,7 +3416,7 @@ val anyAbort: Int < Abort[ErrorType] = 1
val unsafeAgain: Int < Any = anyAbort.orPanic
```

The Abort-specific error handling methods are as follows:
Other error-handling methods are as follows:

```scala
import kyo.*
Expand All @@ -3428,6 +3428,7 @@ trait C
val effect: Int < Abort[A | B | C] = 1

val handled: Result[A | B | C, Int] < Any = effect.result
val mappedError: Int < Abort[String] = effect.mapAbort(_.toString)
val caught: Int < Any = effect.catching(_.toString.size)
val partiallyCaught: Int < Abort[A | B | C] = effect.catchingSome { case err if err.toString.size > 5 => 0 }

Expand All @@ -3439,22 +3440,6 @@ val aToEmpty: Int < Abort[Absent | B | C] = effect.forAbort[A].toEmpty
val aToChoice: Int < (Choice & Abort[B | C]) = effect.forAbort[A].toChoice
```

Note that `mapError` and similar ZIO methods are not really needed, as we can handle those cases with `catching` (and `forAbort[E].catching`):

```scala
import kyo.*

trait A
trait B

val effect: Int < Abort[A] = 1

def fn(err: A): B = new B {}
val mapped: Int < Abort[B] = effect.catching(err => Kyo.fail(fn(err)))

def impureFn(err: A): B < Async = new B {}
val mappedImpure: Int < (Abort[B] & Async) = effect.catching(err => impureFn(err).map(Kyo.fail))
```

## Acknowledgements

Expand Down
34 changes: 34 additions & 0 deletions kyo-combinators/shared/src/main/scala/kyo/Combinators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,22 @@ extension [A, S, E](effect: A < (Abort[E] & S))
): Result[E, A] < S =
Abort.run[E](effect)

/** Handles the Abort effect, transforming caught errors into a new error as determined by mapping function
*
* @return
* A computation that fails with Abort[E1], where E1 is an error type mapped from E
*/
def mapAbort[E1, S1](
fn: E => E1 < S1
)(
using
ct: SafeClassTag[E],
ct1: SafeClassTag[E1],
fl: Flat[A],
fr: Frame
): A < (Abort[E1] & S & S1) =
effect.catching(e => fn(e).map(Kyo.fail))

def forAbort[E1 <: E]: ForAbortOps[A, S, E, E1] = ForAbortOps(effect)

/** Translates the Abort effect to a Choice effect.
Expand Down Expand Up @@ -451,6 +467,24 @@ class ForAbortOps[A, S, E, E1 <: E](effect: A < (Abort[E] & S)) extends AnyVal:
): Result[E1, A] < (S & reduce.SReduced) =
Abort.run[E1](effect.asInstanceOf[A < (Abort[E1 | ER] & S)])

/** Handles a partial Abort[E1] effect, transforming caught errors into a new error as determined by mapping function
*
* @return
* A computation that fails with Abort[E2], where E2 is an error type mapped from E1
*/
def mapAbort[ER, E2, S1](
fn: E1 => E2 < S1
)(
using
ev: E => E1 | ER,
ct: SafeClassTag[E1],
ct1: SafeClassTag[E2],
reduce: Reducible[Abort[ER]],
fl: Flat[A],
fr: Frame
): A < (Abort[E2] & reduce.SReduced & S & S1) =
catching(e => fn(e).map(Kyo.fail))

/** Handles the partial Abort[E1] effect and applies a recovery function to the error.
*
* @return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class AbortCombinatorTest extends Test:
}
}

"handle" - {
"result" - {
"should handle" in {
val effect1 = Abort.fail[String]("failure")
assert(effect1.result.eval == Result.fail("failure"))
Expand All @@ -84,14 +84,6 @@ class AbortCombinatorTest extends Test:
assert(handled.eval == Result.success(Result.success(Result.success(Result.fail("failure")))))
}

"should handle abort" in {
val effect: Int < Abort[String] =
Abort.fail[String]("failure")
val handled: Result[String, Int] < Any =
effect.result
assert(handled.eval == Result.fail("failure"))
}

"should handle union" in {
val failure: Int < Abort[String | Boolean | Double | Int] =
Abort.fail("failure")
Expand All @@ -101,6 +93,22 @@ class AbortCombinatorTest extends Test:
}
}

"mapAbort" - {
"should map abort" in {
val effect1 = Abort.fail[String]("failure")
val effect1Mapped = effect1.mapAbort(_.size)
assert(effect1Mapped.result.eval == Result.fail(7))
}

"should map union abort" in {
val effect1 = Abort.fail[String | Int | Boolean]("failure")
val effect1Mapped = effect1.mapAbort:
case str: String => 0
case _ => -1
assert(effect1Mapped.result.eval == Result.fail(0))
}
}

"convert" - {

"should convert all abort to empty" in {
Expand Down Expand Up @@ -244,6 +252,18 @@ class AbortCombinatorTest extends Test:
}
}

"mapAbort" - {
"should map single Abort" in {
val effect1 = Abort.fail[String | Int | Boolean]("failure")
val effect1Mapped = effect1.forAbort[String].mapAbort(_.size)
assert(effect1Mapped.result.eval == Result.fail(7))

val effect2 = Abort.fail[String | Int | Boolean](1)
val effect2Mapped = effect2.forAbort[String].mapAbort(_.size)
assert(effect2Mapped.result.eval == Result.fail(1))
}
}

"toChoice" - {
"should convert some abort to choice" in {
val effect: Int < Abort[String | Boolean] = Abort.fail("error")
Expand Down

0 comments on commit e23f858

Please sign in to comment.