Skip to content

Commit

Permalink
Add missing docs for Raise operations
Browse files Browse the repository at this point in the history
  • Loading branch information
serras committed Jul 6, 2023
1 parent 17d8d9e commit ccd1a02
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 2 deletions.
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,71 @@ 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].
*
* Note that [Result.isFailure] is `true` only when [block] ends in _logical failure_.
* Any exception thrown within the [Raise] block is re-thrown.
*
* 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 +98,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 +140,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 +177,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 +223,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

0 comments on commit ccd1a02

Please sign in to comment.