Skip to content

Commit

Permalink
Backport Raise & error handlers (#2912)
Browse files Browse the repository at this point in the history
  • Loading branch information
nomisRev committed Feb 6, 2023
1 parent f6c49fc commit c51f4e4
Show file tree
Hide file tree
Showing 56 changed files with 3,132 additions and 172 deletions.
356 changes: 356 additions & 0 deletions arrow-libs/core/arrow-core/api/arrow-core.api

Large diffs are not rendered by default.

322 changes: 218 additions & 104 deletions arrow-libs/core/arrow-core/src/commonMain/kotlin/arrow/core/Either.kt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
package arrow.core

import arrow.core.Either.Right
import arrow.core.raise.OptionRaise
import arrow.core.raise.option
import arrow.typeclasses.Monoid
import arrow.typeclasses.Semigroup
import kotlin.contracts.ExperimentalContracts
Expand All @@ -12,7 +14,7 @@ import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic

/**
*
* <!--- TEST_NAME OptionKnitTest -->
*
* If you have worked with Java at all in the past, it is very likely that you have come across a `NullPointerException` at some time (other languages will throw similarly named errors in such a case). Usually this happens because some method returns `null` when you weren't expecting it and, thus, isn't dealing with that possibility in your client code. A value of `null` is often abused to represent an absent optional value.
* Kotlin tries to solve the problem by getting rid of `null` values altogether, and providing its own special syntax [Null-safety machinery based on `?`](https://kotlinlang.org/docs/reference/null-safety.html).
Expand Down Expand Up @@ -457,6 +459,7 @@ public sealed class Option<out A> {
f()
this
}

is Some -> this
}
}
Expand Down Expand Up @@ -706,6 +709,7 @@ public sealed class Option<out A> {
None -> None
is Some -> Some(b.value.rightIor())
}

is Some -> when (b) {
None -> Some(this.value.leftIor())
is Some -> Some(Pair(this.value, b.value).bothIor())
Expand Down Expand Up @@ -1017,6 +1021,7 @@ public inline fun <A> Option<A>.ensure(error: () -> Unit, predicate: (A) -> Bool
error()
None
}

is None -> this
}
}
Expand Down Expand Up @@ -1103,14 +1108,20 @@ public fun <A, B> Option<Validated<A, B>>.separateValidated(): Pair<Option<A>, O
public fun <A> Option<Iterable<A>>.sequence(): List<Option<A>> =
traverse(::identity)

@Deprecated("sequenceEither is being renamed to sequence to simplify the Arrow API", ReplaceWith("sequence()", "arrow.core.sequence"))
@Deprecated(
"sequenceEither is being renamed to sequence to simplify the Arrow API",
ReplaceWith("sequence()", "arrow.core.sequence")
)
public fun <A, B> Option<Either<A, B>>.sequenceEither(): Either<A, Option<B>> =
sequence()

public fun <A, B> Option<Either<A, B>>.sequence(): Either<A, Option<B>> =
traverse(::identity)

@Deprecated("sequenceValidated is being renamed to sequence to simplify the Arrow API", ReplaceWith("sequence()", "arrow.core.sequence"))
@Deprecated(
"sequenceValidated is being renamed to sequence to simplify the Arrow API",
ReplaceWith("sequence()", "arrow.core.sequence")
)
public fun <A, B> Option<Validated<A, B>>.sequenceValidated(): Validated<A, Option<B>> =
sequence()

Expand Down Expand Up @@ -1185,6 +1196,7 @@ public fun <A> Option<A>.combine(SGA: Semigroup<A>, b: Option<A>): Option<A> =
is Some -> Some(SGA.run { value.combine(b.value) })
None -> this
}

None -> b
}

Expand All @@ -1194,3 +1206,53 @@ public operator fun <A : Comparable<A>> Option<A>.compareTo(other: Option<A>): I
other.fold({ 1 }, { a2 -> a1.compareTo(a2) })
}
)

/**
* Recover from any [None] if encountered.
*
* The recover DSL allows you to recover from any [None] value by:
* - Computing a fallback value [A]
* - Shifting a _new error_ of [None] into the [Option].
*
* ```kotlin
* import arrow.core.Option
* import arrow.core.none
* import arrow.core.Some
* import arrow.core.recover
* import io.kotest.matchers.shouldBe
*
* fun test() {
* val error: Option<Int> = none()
* val fallback: Option<Int> = error.recover { 5 }
* fallback shouldBe Some(5)
* }
* ```
* <!--- KNIT example-option-24.kt -->
* <!--- TEST lines.isEmpty() -->
*
* When shifting a new error [None] into the [Option]:
*
* ```kotlin
* import arrow.core.Option
* import arrow.core.none
* import arrow.core.Some
* import arrow.core.recover
* import io.kotest.matchers.shouldBe
*
* fun test() {
* val error: Option<Int> = none()
* fun fallback(): Option<Int> = Some(5)
* fun failure(): Option<Int> = none()
*
* error.recover { fallback().bind() } shouldBe Some(5)
* error.recover { failure().bind() } shouldBe none()
* }
* ```
* <!--- KNIT example-option-25.kt -->
* <!--- TEST lines.isEmpty() -->
*/
public inline fun <A> Option<A>.recover(recover: OptionRaise.(None) -> A): Option<A> =
when (this@recover) {
is None -> option { recover(this, None) }
is Some -> this@recover
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,29 @@ import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.coroutines.RestrictsSuspension

@Deprecated(deprecateInFavorOfEffectScope, ReplaceWith("EffectScope<E>", "arrow.core.continuations.EffectScope"))
@Deprecated(
"EitherEffect is replaced with arrow.core.raise.Raise<E>",
ReplaceWith("Raise<E>", "arrow.core.raise.Raise")
)
public fun interface EitherEffect<E, A> : Effect<Either<E, A>> {

public suspend fun <B> Either<E, B>.bind(): B =
when (this) {
is Either.Right -> value
is Left -> control().shift(this@bind)
}

public suspend fun <B> Validated<E, B>.bind(): B =
when (this) {
is Validated.Valid -> value
is Validated.Invalid -> control().shift(Left(value))
}

public suspend fun <B> Result<B>.bind(transform: (Throwable) -> E): B =
fold(::identity) { throwable ->
control().shift(transform(throwable).left())
}

/**
* Ensure check if the [value] is `true`,
* and if it is it allows the `either { }` binding to continue.
Expand Down Expand Up @@ -85,34 +88,41 @@ public fun interface EitherEffect<E, A> : Effect<Either<E, A>> {
* ```
* <!--- KNIT example-either-computations-02.kt -->
*/
@Deprecated(deprecateInFavorOfEffectScope)
@Deprecated(
"Replaced by Raise, replace arrow.core.computations.ensureNotNull to arrow.core.raise.ensureNotNull",
ReplaceWith(
"ensureNotNull(value, orLeft)",
"import arrow.core.raise.ensureNotNull"
)
)
@OptIn(ExperimentalContracts::class) // Contracts not available on open functions, so made it top-level.
public suspend fun <E, B : Any> EitherEffect<E, *>.ensureNotNull(value: B?, orLeft: () -> E): B {
contract {
returns() implies (value != null)
}

return value ?: orLeft().left().bind()
}

@Deprecated(deprecatedInFavorOfEagerEffectScope, ReplaceWith("EagerEffectScope<E>", "arrow.core.continuations.EagerEffectScope"))
@Deprecated(
"RestrictedEitherEffect is replaced with arrow.core.raise.Raise<E>",
ReplaceWith("Raise<E>", "arrow.core.raise.Raise")
)
@RestrictsSuspension
public fun interface RestrictedEitherEffect<E, A> : EitherEffect<E, A>

@Deprecated(deprecateInFavorOfEffectOrEagerEffect, ReplaceWith("either", "arrow.core.continuations.either"))
@Deprecated(eitherDSLDeprecation, ReplaceWith("either", "arrow.core.raise.either"))
@Suppress("ClassName")
public object either {
@Deprecated(deprecateInFavorOfEagerEffect, ReplaceWith("either.eager(c)", "arrow.core.continuations.either"))
@Deprecated(eitherDSLDeprecation, ReplaceWith("either(c)", "arrow.core.raise.either"))
public inline fun <E, A> eager(crossinline c: suspend RestrictedEitherEffect<E, *>.() -> A): Either<E, A> =
Effect.restricted(eff = { RestrictedEitherEffect { it } }, f = c, just = { it.right() })

@Deprecated(deprecateInFavorOfEffect, ReplaceWith("either(c)", "arrow.core.continuations.either"))
@Deprecated(eitherDSLDeprecation, ReplaceWith("either(c)", "arrow.core.raise.either"))
public suspend inline operator fun <E, A> invoke(crossinline c: suspend EitherEffect<E, *>.() -> A): Either<E, A> =
Effect.suspended(eff = { EitherEffect { it } }, f = c, just = { it.right() })
}

internal const val deprecatedInFavorOfEagerEffectScope: String = "Deprecated in favor of Eager Effect DSL: EagerEffectScope"
internal const val deprecateInFavorOfEffectScope: String = "Deprecated in favor of Effect DSL: EffectScope"
internal const val deprecateInFavorOfEffect: String = "Deprecated in favor of the Effect Runtime"
internal const val deprecateInFavorOfEagerEffect: String = "Deprecated in favor of the EagerEffect Runtime"
internal const val deprecateInFavorOfEffectOrEagerEffect: String = "Deprecated in favor of the Effect or EagerEffect Runtime"
private const val eitherDSLDeprecation =
"The either DSL has been moved to arrow.core.raise.either.\n" +
"Replace import arrow.core.computations.* with arrow.core.raise.*"
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,28 @@ import arrow.continuations.Effect
import arrow.core.Eval
import kotlin.coroutines.RestrictsSuspension

@Deprecated(deprecateInFavorOfEffectScope, ReplaceWith("EffectScope<E>", "arrow.core.continuations.EffectScope"))
@Deprecated("EvalEffect is redundant. Use Eval#value directly instead")
public fun interface EvalEffect<A> : Effect<Eval<A>> {
@Deprecated(
"EvalEffect is redundant. Use Eval#value directly instead",
ReplaceWith("this.value()")
)
public suspend fun <B> Eval<B>.bind(): B =
value()
}

@Deprecated(deprecatedInFavorOfEagerEffectScope, ReplaceWith("EagerEffectScope<E>", "arrow.core.continuations.EagerEffectScope"))
@Deprecated("RestrictedEvalEffect is redundant. Use Eval#value directly instead")
@RestrictsSuspension
public fun interface RestrictedEvalEffect<A> : EvalEffect<A>

@Deprecated(deprecateInFavorOfEffectOrEagerEffect)
@Deprecated("EvalEffect is redundant. Use Eval#value directly instead")
@Suppress("ClassName")
public object eval {
@Deprecated(deprecateInFavorOfEagerEffect, ReplaceWith("eagerEffect(func)", "arrow.core.continuations.eagerEffect"))
@Deprecated("EvalEffect is redundant. Use Eval#value directly instead")
public inline fun <A> eager(crossinline func: suspend RestrictedEvalEffect<A>.() -> A): Eval<A> =
Effect.restricted(eff = { RestrictedEvalEffect { it } }, f = func, just = Eval.Companion::now)

@Deprecated(deprecateInFavorOfEffect, ReplaceWith("effect(func)", "arrow.core.continuations.effect"))
@Deprecated("EvalEffect is redundant. Use Eval#value) directly instead")
public suspend inline operator fun <A> invoke(crossinline func: suspend EvalEffect<*>.() -> A): Eval<A> =
Effect.suspended(eff = { EvalEffect { it } }, f = func, just = Eval.Companion::now)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.coroutines.RestrictsSuspension

@Deprecated(deprecateInFavorOfEffectScope)
@Deprecated(
"NullableEffect<A> is replaced with arrow.core.raise.NullableRaise",
ReplaceWith("NullableRaise", "arrow.core.raise.NullableRaise")
)
public fun interface NullableEffect<A> : Effect<A?> {
public suspend fun <B> B?.bind(): B =
this ?: control().shift(null)
Expand Down Expand Up @@ -68,7 +71,13 @@ public fun interface NullableEffect<A> : Effect<A?> {
* ```
* <!--- KNIT example-nullable-computations-02.kt -->
*/
@Deprecated(deprecateInFavorOfEffectScope, ReplaceWith("ensureNotNull", "arrow.core.continuations.ensureNotNull"))
@Deprecated(
"Replaced by Raise, replace arrow.core.computations.ensureNotNull to arrow.core.raise.ensureNotNull",
ReplaceWith(
"ensureNotNull(value)",
"import arrow.core.raise.ensureNotNull"
)
)
@OptIn(ExperimentalContracts::class) // Contracts not available on open functions, so made it top-level.
public suspend fun <B : Any> NullableEffect<*>.ensureNotNull(value: B?): B {
contract {
Expand All @@ -78,18 +87,25 @@ public suspend fun <B : Any> NullableEffect<*>.ensureNotNull(value: B?): B {
return value ?: control().shift(null)
}

@Deprecated(deprecatedInFavorOfEagerEffectScope)
@Deprecated(
"RestrictedNullableEffect<A> is replaced with arrow.core.raise.NullableRaise",
ReplaceWith("NullableRaise", "arrow.core.raise.NullableRaise")
)
@RestrictsSuspension
public fun interface RestrictedNullableEffect<A> : NullableEffect<A>

@Deprecated(deprecateInFavorOfEffectOrEagerEffect, ReplaceWith("nullable", "arrow.core.continuations.nullable"))
@Deprecated(nullableDSLDeprecation, ReplaceWith("nullable", "arrow.core.raise.nullable"))
@Suppress("ClassName")
public object nullable {
@Deprecated(deprecateInFavorOfEagerEffect, ReplaceWith("nullable.eager(func)", "arrow.core.continuations.nullable"))
@Deprecated(nullableDSLDeprecation, ReplaceWith("nullable(func)", "arrow.core.raise.nullable"))
public inline fun <A> eager(crossinline func: suspend RestrictedNullableEffect<A>.() -> A?): A? =
Effect.restricted(eff = { RestrictedNullableEffect { it } }, f = func, just = { it })

@Deprecated(deprecateInFavorOfEffect, ReplaceWith("nullable(func)", "arrow.core.continuations.nullable"))
@Deprecated(nullableDSLDeprecation, ReplaceWith("nullable(func)", "arrow.core.raise.nullable"))
public suspend inline operator fun <A> invoke(crossinline func: suspend NullableEffect<*>.() -> A?): A? =
Effect.suspended(eff = { NullableEffect { it } }, f = func, just = { it })
}

private const val nullableDSLDeprecation =
"The nullable DSL has been moved to arrow.core.raise.nullable.\n" +
"Replace import arrow.core.computations.* with arrow.core.raise.*"
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.coroutines.RestrictsSuspension

@Deprecated(deprecateInFavorOfEffectScope, ReplaceWith("EffectScope<E>", "arrow.core.continuations.EffectScope"))
@Deprecated(
"OptionEffect<A> is replaced with arrow.core.raise.OptionRaise",
ReplaceWith("OptionRaise", "arrow.core.raise.OptionRaise")
)
public fun interface OptionEffect<A> : Effect<Option<A>> {
public suspend fun <B> Option<B>.bind(): B =
fold({ control().shift(None) }, ::identity)
Expand Down Expand Up @@ -66,7 +69,13 @@ public fun interface OptionEffect<A> : Effect<Option<A>> {
* ```
* <!--- KNIT example-option-computations-02.kt -->
*/
@Deprecated(deprecateInFavorOfEffectScope)
@Deprecated(
"Replaced by Raise, replace arrow.core.computations.ensureNotNull to arrow.core.raise.ensureNotNull",
ReplaceWith(
"ensureNotNull(value)",
"import arrow.core.raise.ensureNotNull"
)
)
@OptIn(ExperimentalContracts::class) // Contracts not available on open functions, so made it top-level.
public suspend fun <B : Any> OptionEffect<*>.ensureNotNull(value: B?): B {
contract {
Expand All @@ -76,18 +85,32 @@ public suspend fun <B : Any> OptionEffect<*>.ensureNotNull(value: B?): B {
return value ?: (this as OptionEffect<Any?>).control().shift(None)
}

@Deprecated(deprecatedInFavorOfEagerEffectScope, ReplaceWith("EagerEffectScope<E>", "arrow.core.continuations.EagerEffectScope"))
@RestrictsSuspension
@Deprecated(
"RestrictedEitherEffect is replaced with arrow.core.raise.OptionRaise",
ReplaceWith("OptionRaise", "arrow.core.raise.OptionRaise")
)@RestrictsSuspension
public fun interface RestrictedOptionEffect<A> : OptionEffect<A>

@Deprecated(deprecateInFavorOfEffectOrEagerEffect, ReplaceWith("option", "arrow.core.continuations.option"))
@Deprecated(
optionDSLDeprecation,
ReplaceWith("option", "arrow.core.raise.option")
)
@Suppress("ClassName")
public object option {
@Deprecated(deprecateInFavorOfEagerEffect, ReplaceWith("option.eager(func)", "arrow.core.continuations.option"))
@Deprecated(
optionDSLDeprecation,
ReplaceWith("option(func)", "arrow.core.raise.option")
)
public inline fun <A> eager(crossinline func: suspend RestrictedOptionEffect<A>.() -> A): Option<A> =
Effect.restricted(eff = { RestrictedOptionEffect { it } }, f = func, just = { Option.fromNullable(it) })

@Deprecated(deprecateInFavorOfEffect, ReplaceWith("option(func)", "arrow.core.continuations.option"))
public suspend inline operator fun <A> invoke(crossinline func: suspend OptionEffect<*>.() -> A?): Option<A> =

@Deprecated(
optionDSLDeprecation,
ReplaceWith("option(func)", "arrow.core.raise.option")
) public suspend inline operator fun <A> invoke(crossinline func: suspend OptionEffect<*>.() -> A?): Option<A> =
Effect.suspended(eff = { OptionEffect { it } }, f = func, just = { Option.fromNullable(it) })
}

private const val optionDSLDeprecation =
"The option DSL has been moved to arrow.core.raise.option.\n" +
"Replace import arrow.core.computations.* with arrow.core.raise.*"
Loading

0 comments on commit c51f4e4

Please sign in to comment.