Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add withError and (Eager)Effect.mapError #3059

Merged
merged 4 commits into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions arrow-libs/core/arrow-core/api/arrow-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -3441,6 +3441,8 @@ public final class arrow/core/raise/RaiseKt {
public static final fun getOrNull (Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public static final fun getOrNull (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun ior (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)Larrow/core/Ior;
public static final fun mapError (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1;
public static final fun mapError (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lkotlin/jvm/functions/Function2;
public static final fun mapOrAccumulate (Larrow/core/raise/Raise;Larrow/core/NonEmptyList;Lkotlin/jvm/functions/Function2;)Larrow/core/NonEmptyList;
public static final fun mapOrAccumulate (Larrow/core/raise/Raise;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;)Ljava/util/List;
public static final fun mapOrAccumulate (Larrow/core/raise/Raise;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Ljava/util/List;
Expand Down Expand Up @@ -3470,6 +3472,7 @@ public final class arrow/core/raise/RaiseKt {
public static final fun toValidated (Lkotlin/jvm/functions/Function1;)Larrow/core/Validated;
public static final fun toValidated (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 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;
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/Function7;)Ljava/lang/Object;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import kotlin.jvm.JvmName
/**
* Catch the raised value [Error] of the `Effect`.
* You can either return a value a new value of [A],
* or short-circuit the effect by raising with a value of [Error],
* or short-circuit the effect by raising with a value of [OtherError],
* or raise an exception into [suspend].
*
* ```kotlin
Expand Down Expand Up @@ -96,6 +96,29 @@ public fun <Error, A> Effect<Error, A>.catch(): Effect<Error, Result<A>> =
public suspend inline infix fun <Error, A> Effect<Error, A>.getOrElse(recover: suspend (error: Error) -> A): A =
recover({ invoke() }) { recover(it) }

/**
* Transform the raised value [Error] of the `Effect` into [OtherError],
* or raise an exception into [suspend].
* This results in an `Effect` that returns a value of [A] or raises [OtherError].
*
* ```kotlin
* import arrow.core.raise.effect
* import arrow.core.raise.mapError
*
* object User
* object Error
*
* val error = effect<Error, User> { raise(Error) } // Raise(error)
*
* val a = error.mapError<Error, String, User> { error -> "some-failure" } // Raise(some-failure)
* val b = error.mapError<Error, String, User>(Any::toString) // Raise(Error)
* val c = error.mapError<Error, Nothing, User> { error -> throw RuntimeException("BOOM") } // Exception(BOOM)
* ```
* <!--- KNIT example-effect-error-04.kt -->
*/
public infix fun <Error, OtherError, A> Effect<Error, A>.mapError(transform: suspend (error: Error) -> OtherError): Effect<OtherError, A> =
effect { withError({ transform(it) }) { invoke() } }

public infix fun <Error, OtherError, A> EagerEffect<Error, A>.recover(@BuilderInference recover: Raise<OtherError>.(error: Error) -> A): EagerEffect<OtherError, A> =
eagerEffect { recover({ invoke() }) { recover(it) } }

Expand All @@ -110,3 +133,24 @@ public inline infix fun <reified T : Throwable, Error, A> EagerEffect<Error, A>.

public inline infix fun <Error, A> EagerEffect<Error, A>.getOrElse(recover: (error: Error) -> A): A =
recover({ invoke() }, recover)

/**
* Transform the raised value [Error] of the `EagerEffect` into [OtherError].
* This results in an `EagerEffect` that returns a value of [A] or raises [OtherError].
*
* ```kotlin
* import arrow.core.raise.eagerEffect
* import arrow.core.raise.mapError
*
* object User
* object Error
*
* val error = eagerEffect<Error, User> { raise(Error) } // Raise(error)
*
* val a = error.mapError<Error, String, User> { error -> "some-failure" } // Raise(some-failure)
* val b = error.mapError<Error, String, User>(Any::toString) // Raise(Error)
* ```
* <!--- KNIT example-effect-error-05.kt -->
*/
public infix fun <Error, OtherError, A> EagerEffect<Error, A>.mapError(transform: (error: Error) -> OtherError): EagerEffect<OtherError, A> =
eagerEffect { withError({ transform(it) }) { invoke() } }
Original file line number Diff line number Diff line change
Expand Up @@ -453,3 +453,36 @@ public inline fun <Error, B : Any> Raise<Error>.ensureNotNull(value: B?, raise:
}
return value ?: raise(raise())
}

/**
* Execute the [Raise] context function resulting in [A] or any _logical error_ of type [OtherError],
* and transform any raised [OtherError] into [Error], which is raised to the outer [Raise].
*
* <!--- INCLUDE
* import arrow.core.Either
* import arrow.core.raise.either
* import arrow.core.raise.withError
* import io.kotest.matchers.shouldBe
* -->
* ```kotlin
* fun test() {
* either<Int, String> {
* withError(String::length) {
* raise("failed")
* }
* } shouldBe Either.Left(6)
* }
* ```
* <!--- KNIT example-raise-dsl-11.kt -->
* <!--- TEST lines.isEmpty() -->
*/
@RaiseDSL
public inline fun <Error, OtherError, A> Raise<Error>.withError(
transform: (OtherError) -> Error,
@BuilderInference block: Raise<OtherError>.() -> A
): A {
contract {
callsInPlace(transform, AT_MOST_ONCE)
}
return recover(block) { raise(transform(it)) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,22 @@ class EagerEffectSpec : StringSpec({
.fold({ unreachable() }, { unreachable() })
}.message shouldBe "Boom!"
}

"mapError - raise and transform error" {
checkAll(Arb.long(), Arb.string()) { l, s ->
(eagerEffect<Long, Int> {
raise(l)
} mapError { ll ->
ll shouldBe l
s
}).fold(::identity) { unreachable() } shouldBe s
}
}

"mapError - success" {
checkAll(Arb.int()) { i ->
(eagerEffect<Long, Int> { i } mapError { unreachable() })
.get() shouldBe i
}
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,27 @@ class EffectSpec : StringSpec({
}) { fail("Cannot be here") }
}.message shouldStartWith "raise or bind was called outside of its DSL scope"
}

"mapError - raise and transform error" {
checkAll(
Arb.long().suspend(),
Arb.string().suspend()
) { l, s ->
(effect<Long, Int> {
raise(l())
} mapError { ll ->
ll shouldBe l()
s()
}).fold(::identity) { unreachable() } shouldBe s()
}
}

"mapError - success" {
checkAll(Arb.int().suspend()) { i ->
(effect<Long, Int> { i() } mapError { unreachable() })
.get() shouldBe i()
}
}
})

private data class Failure(val msg: String)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// This file was automatically generated from ErrorHandlers.kt by Knit tool. Do not edit.
package arrow.core.examples.exampleEffectError04

import arrow.core.raise.effect
import arrow.core.raise.mapError

object User
object Error

val error = effect<Error, User> { raise(Error) } // Raise(error)

val a = error.mapError<Error, String, User> { error -> "some-failure" } // Raise(some-failure)
val b = error.mapError<Error, String, User>(Any::toString) // Raise(Error)
val c = error.mapError<Error, Nothing, User> { error -> throw RuntimeException("BOOM") } // Exception(BOOM)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// This file was automatically generated from ErrorHandlers.kt by Knit tool. Do not edit.
package arrow.core.examples.exampleEffectError05

import arrow.core.raise.eagerEffect
import arrow.core.raise.mapError

object User
object Error

val error = eagerEffect<Error, User> { raise(Error) } // Raise(error)

val a = error.mapError<Error, String, User> { error -> "some-failure" } // Raise(some-failure)
val b = error.mapError<Error, String, User>(Any::toString) // Raise(Error)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// This file was automatically generated from Raise.kt by Knit tool. Do not edit.
package arrow.core.examples.exampleRaiseDsl11

import arrow.core.Either
import arrow.core.raise.either
import arrow.core.raise.withError
import io.kotest.matchers.shouldBe

fun test() {
either<Int, String> {
withError(String::length) {
raise("failed")
}
} shouldBe Either.Left(6)
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class RaiseKnitTest : StringSpec({
arrow.core.examples.exampleRaiseDsl10.test()
}

"ExampleRaiseDsl11" {
arrow.core.examples.exampleRaiseDsl11.test()
}

}) {
override fun timeout(): Long = 1000
}