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

Backport Raise & error handlers #2912

Merged
merged 17 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from 16 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
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