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 missing docs for Raise operations #3082

Merged
merged 2 commits into from
Jul 7, 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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package arrow.core.raise
import arrow.atomic.Atomic
import arrow.atomic.updateAndGet
import arrow.core.Either
import arrow.core.EmptyValue.combine
import arrow.core.Ior
import arrow.core.NonEmptyList
import arrow.core.NonEmptySet
Expand All @@ -22,18 +21,69 @@ import kotlin.experimental.ExperimentalTypeInference
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName

/**
* Runs a computation [block] using [Raise], and return its outcome as [Either].
* - [Either.Right] represents success,
* - [Either.Left] represents logical failure.
*
* This function re-throws any exceptions thrown within the [Raise] block.
*
* 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> either(@BuilderInference block: Raise<Error>.() -> A): Either<Error, A> =
fold({ block.invoke(this) }, { Either.Left(it) }, { Either.Right(it) })

/**
* Runs a computation [block] using [Raise], and return its outcome as nullable type,
* where `null` represents logical failure.
*
* This function re-throws any exceptions thrown within the [Raise] block.
*
* 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 <A> nullable(block: NullableRaise.() -> A): A? =
merge { block(NullableRaise(this)) }

/**
* Runs a computation [block] using [Raise], and return its outcome as [Result].
*
*
* 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 <A> result(block: ResultRaise.() -> A): Result<A> =
fold({ block(ResultRaise(this)) }, Result.Companion::failure, Result.Companion::failure, Result.Companion::success)

/**
* Runs a computation [block] using [Raise], and return its outcome as [Option].
* - [Some] represents success,
* - [None] represents logical failure.
*
* This function re-throws any exceptions thrown within the [Raise] block.
*
* 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 <A> option(block: OptionRaise.() -> A): Option<A> =
fold({ block(OptionRaise(this)) }, ::identity, ::Some)

/**
* Runs a computation [block] using [Raise], and return its outcome as [Ior].
* - [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].
*
* 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> ior(noinline combineError: (Error, Error) -> Error, @BuilderInference block: IorRaise<Error>.() -> A): Ior<Error, A> {
val state: Atomic<Option<Error>> = Atomic(None)
return fold<Error, A, Ior<Error, A>>(
Expand All @@ -46,6 +96,10 @@ public inline fun <Error, A> ior(noinline combineError: (Error, Error) -> Error,

public typealias Null = Nothing?

/**
* Implementation of [Raise] used by [nullable].
* You should never use this directly.
*/
public class NullableRaise(private val raise: Raise<Null>) : Raise<Null> by raise {
@RaiseDSL
public fun ensure(value: Boolean): Unit = ensure(value) { null }
Expand Down Expand Up @@ -84,6 +138,10 @@ public class NullableRaise(private val raise: Raise<Null>) : Raise<Null> by rais
}
}

/**
* Implementation of [Raise] used by [result].
* You should never use this directly.
*/
public class ResultRaise(private val raise: Raise<Throwable>) : Raise<Throwable> by raise {
@RaiseDSL
public fun <A> Result<A>.bind(): A = fold(::identity) { raise(it) }
Expand Down Expand Up @@ -117,6 +175,10 @@ public class ResultRaise(private val raise: Raise<Throwable>) : Raise<Throwable>
)
}

/**
* Implementation of [Raise] used by [option].
* You should never use this directly.
*/
public class OptionRaise(private val raise: Raise<None>) : Raise<None> by raise {
@RaiseDSL
public fun <A> Option<A>.bind(): A = getOrElse { raise(None) }
Expand Down Expand Up @@ -159,6 +221,10 @@ public class OptionRaise(private val raise: Raise<None>) : Raise<None> by raise
}
}

/**
* Implementation of [Raise] used by [ior].
* You should never use this directly.
*/
public class IorRaise<Error> @PublishedApi internal constructor(
@PublishedApi internal val combineError: (Error, Error) -> Error,
private val state: Atomic<Option<Error>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ public suspend fun <Error, A, B> Effect<Error, A>.fold(
return fold({ invoke() }, { catch(it) }, { recover(it) }, { transform(it) })
}

/**
* `invoke` the [Effect] and [fold] the result:
* - _success_ [transform] result of [A] to a value of [B].
* - _raised_ [recover] from `raised` value of [Error] to a value of [B].
*
* This function re-throws any exceptions thrown within the [Effect].
*/
public suspend fun <Error, A, B> Effect<Error, A>.fold(
recover: suspend (error: Error) -> B,
transform: suspend (value: A) -> B,
Expand All @@ -48,6 +55,15 @@ public suspend fun <Error, A, B> Effect<Error, A>.fold(
return fold({ throw it }, recover, transform)
}

/**
* `invoke` the [EagerEffect] and [fold] the result:
* - _success_ [transform] result of [A] to a value of [B].
* - _raised_ [recover] from `raised` value of [Error] to a value of [B].
* - _exception_ [catch] from [Throwable] by transforming value into [B].
*
* This method should never be wrapped in `try`/`catch` as it will not throw any unexpected errors,
* it will only result in [CancellationException], or fatal exceptions such as `OutOfMemoryError`.
*/
public inline fun <Error, A, B> EagerEffect<Error, A>.fold(
catch: (throwable: Throwable) -> B,
recover: (error: Error) -> B,
Expand All @@ -61,6 +77,13 @@ public inline fun <Error, A, B> EagerEffect<Error, A>.fold(
return fold({ invoke(this) }, catch, recover, transform)
}

/**
* `invoke` the [EagerEffect] and [fold] the result:
* - _success_ [transform] result of [A] to a value of [B].
* - _raised_ [recover] from `raised` value of [Error] to a value of [B].
*
* This function re-throws any exceptions thrown within the [Effect].
*/
public inline fun <Error, A, B> EagerEffect<Error, A>.fold(recover: (error: Error) -> B, transform: (value: A) -> B): B {
contract {
callsInPlace(recover, AT_MOST_ONCE)
Expand All @@ -69,6 +92,14 @@ public inline fun <Error, A, B> EagerEffect<Error, A>.fold(recover: (error: Erro
return fold({ throw it }, recover, transform)
}

/**
* The most general way to execute a computation using [Raise].
* Depending on the outcome of the block, one of the two continuations is run:
* - _success_ [transform] result of [A] to a value of [B].
* - _raised_ [recover] from `raised` value of [Error] to a value of [B].
*
* This function re-throws any exceptions thrown within the [Raise] block.
*/
@JvmName("_foldOrThrow")
public inline fun <Error, A, B> fold(
@BuilderInference block: Raise<Error>.() -> A,
Expand All @@ -82,6 +113,16 @@ public inline fun <Error, A, B> fold(
return fold(block, { throw it }, recover, transform)
}

/**
* The most general way to execute a computation using [Raise].
* Depending on the outcome of the block, one of the three continuations is run:
* - _success_ [transform] result of [A] to a value of [B].
* - _raised_ [recover] from `raised` value of [Error] to a value of [B].
* - _exception_ [catch] from [Throwable] by transforming value into [B].
*
* This method should never be wrapped in `try`/`catch` as it will not throw any unexpected errors,
* it will only result in [CancellationException], or fatal exceptions such as `OutOfMemoryError`.
*/
@JvmName("_fold")
public inline fun <Error, A, B> fold(
@BuilderInference block: Raise<Error>.() -> A,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ import kotlin.jvm.JvmName

/** Run the [Effect] by returning [Either.Right] of [A], or [Either.Left] of [Error]. */
public suspend fun <Error, A> Effect<Error, A>.toEither(): Either<Error, A> = either { invoke() }
/** Run the [EagerEffect] by returning [Either.Right] of [A], or [Either.Left] of [Error]. */
public fun <Error, A> EagerEffect<Error, A>.toEither(): Either<Error, A> = either { invoke() }

/** Run the [Effect] by returning [Validated.Valid] of [A], or [Validated.Invalid] of [Error]. */
@Deprecated(ValidatedDeprMsg, ReplaceWith("toEither()"))
public suspend fun <Error, A> Effect<Error, A>.toValidated(): Validated<Error, A> = fold({ Validated.Invalid(it) }) { Validated.Valid(it) }
/** Run the [EagerEffect] by returning [Validated.Valid] of [A], or [Validated.Invalid] of [Error]. */
@Deprecated(ValidatedDeprMsg, ReplaceWith("toEither()"))
public fun <Error, A> EagerEffect<Error, A>.toValidated(): Validated<Error, A> = fold({ Validated.Invalid(it) }) { Validated.Valid(it) }

/** Run the [Effect] by returning [Ior.Right] of [A], or [Ior.Left] of [Error]. */
public suspend fun <Error, A> Effect<Error, A>.toIor(): Ior<Error, A> = fold({ Ior.Left(it) }) { Ior.Right(it) }
/** Run the [EagerEffect] by returning [Ior.Right] of [A], or [Ior.Left] of [Error]. */
public fun <Error, A> EagerEffect<Error, A>.toIor(): Ior<Error, A> = fold({ Ior.Left(it) }) { Ior.Right(it) }

@Deprecated(
Expand All @@ -45,14 +48,17 @@ public fun <Error, A> EagerEffect<Error, A>.getOrNull(): A? = getOrElse { null }

/** Run the [Effect] by returning [Option] of [A], [recover] run the fallback lambda and returning its result of [Option] of [A]. */
public suspend fun <Error, A> Effect<Error, A>.toOption(recover: suspend (error: Error) -> Option<A>): Option<A> = fold(recover) { Some(it) }
/** Run the [EagerEffect] by returning [Option] of [A], [recover] run the fallback lambda and returning its result of [Option] of [A]. */
public inline fun <Error, A> EagerEffect<Error, A>.toOption(recover: (error: Error) -> Option<A>): Option<A> = fold(recover) { Some(it) }

/** Run the [Effect] by returning [Result] of [A], [recover] run the fallback lambda and returning its result of [Result] of [A]. */
public suspend fun <Error, A> Effect<Error, A>.toResult(recover: suspend (error: Error) -> Result<A>): Result<A> =
fold({ Result.failure(it) }, { recover(it) }, { Result.success(it) })
/** Run the [EagerEffect] by returning [Result] of [A], [recover] run the fallback lambda and returning its result of [Result] of [A]. */
public inline fun <Error, A> EagerEffect<Error, A>.toResult(recover: (error: Error) -> Result<A>): Result<A> =
fold({ Result.failure(it) }, { recover(it) }, { Result.success(it) })

/** Run the [Effect] by returning [Result] of [A], or [Result.Failure] if raised with [Throwable]. */
public suspend fun <A> Effect<Throwable, A>.toResult(): Result<A> = result { invoke() }
/** Run the [EagerEffect] by returning [Result] of [A], or [Result.Failure] if raised with [Throwable]. */
public fun <A> EagerEffect<Throwable, A>.toResult(): Result<A> = result { invoke() }
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ public annotation class RaiseDSL
*
* The [Raise] DSL allows you to work with _logical failures_ of type [Error].
* A _logical failure_ does not necessarily mean that the computation has failed,
* but that it has stopped or _short-circuited_.
* but that it has stopped or _short-circuited_. The Arrow website has a
* [guide](https://arrow-kt.io/learn/typed-errors/working-with-typed-errors/)
* introducing [Raise] and how to use it effectively.
*
* The [Raise] DSL allows you to [raise] _logical failure_ of type [Error], and you can [recover] from them.
*
Expand Down Expand Up @@ -248,6 +250,13 @@ public interface Raise<in Error> {
*/
public operator fun <A> EagerEffect<Error, A>.invoke(): A = invoke(this@Raise)

/**
* Invoke an [EagerEffect] inside `this` [Raise] context.
* Any _logical failure_ is raised in `this` [Raise] context,
* and thus short-circuits the computation.
*
* @see [recover] if you want to attempt to recover from any _logical failure_.
*/
@RaiseDSL
public fun <A> EagerEffect<Error, A>.bind(): A = invoke(this@Raise)

Expand All @@ -260,6 +269,13 @@ public interface Raise<in Error> {
*/
public suspend operator fun <A> Effect<Error, A>.invoke(): A = invoke(this@Raise)

/**
* Invoke an [Effect] inside `this` [Raise] context.
* Any _logical failure_ raised are raised in `this` [Raise] context,
* and thus short-circuits the computation.
*
* @see [recover] if you want to attempt to recover from any _logical failure_.
*/
@RaiseDSL
public suspend fun <A> Effect<Error, A>.bind(): A = invoke(this@Raise)

Expand Down Expand Up @@ -296,6 +312,11 @@ public interface Raise<in Error> {
is Either.Right -> value
}

/**
* Extracts all the values in the [Map], raising every [Either.Left]
* as a _logical failure_. In other words, executed [bind] over every
* value in this [Map].
*/
public fun <K, A> Map<K, Either<Error, A>>.bindAll(): Map<K, A> =
mapValues { (_, a) -> a.bind() }

Expand All @@ -306,14 +327,29 @@ public interface Raise<in Error> {
is Validated.Valid -> value
}

/**
* Extracts all the values in the [Iterable], raising every [Either.Left]
* as a _logical failure_. In other words, executed [bind] over every
* value in this [Iterable].
*/
@RaiseDSL
public fun <A> Iterable<Either<Error, A>>.bindAll(): List<A> =
map { it.bind() }

/**
* Extracts all the values in the [NonEmptyList], raising every [Either.Left]
* as a _logical failure_. In other words, executed [bind] over every
* value in this [NonEmptyList].
*/
@RaiseDSL
public fun <A> NonEmptyList<Either<Error, A>>.bindAll(): NonEmptyList<A> =
map { it.bind() }

/**
* Extracts all the values in the [NonEmptySet], raising every [Either.Left]
* as a _logical failure_. In other words, executed [bind] over every
* value in this [NonEmptySet].
*/
@RaiseDSL
public fun <A> NonEmptySet<Either<Error, A>>.bindAll(): NonEmptySet<A> =
map { it.bind() }.toNonEmptySet()
Expand Down
Loading