app.cash.quiver.extensions.Either.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lib Show documentation
Show all versions of lib Show documentation
Quiver library providing extension methods and type aliases to improve Arrow
The newest version!
package app.cash.quiver.extensions
import app.cash.quiver.raise.outcome
import arrow.core.Either
import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.flatMap
import arrow.core.getOrElse
import arrow.core.identity
import arrow.core.recover
import arrow.core.right
import arrow.core.toOption
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.experimental.ExperimentalTypeInference
import app.cash.quiver.extensions.traverse as quiverTraverse
/**
* Retrieves the Right hand of an Either, or throws the Left hand error
*/
fun Either.orThrow() = this.getOrElse { t -> throw t }
/**
* Turns a nullable value into an [Either]. This is useful for building validation functions.
*
* @return [Either.Left] if the value is null, [Either.Right] if the value is not null.
* @param label Optional [String] to identify the nullable value being evaluated, used in the failure message.
*/
fun B?.validateNotNull(label: Option = None): Either = this.toEither {
IllegalArgumentException("Value${label.map { " (`$it`)" }.getOrElse { "" }} should not be null")
}
/**
* Returns the first successful either, otherwise the last failure
*/
inline fun Either.or(f: () -> Either): Either = when (this) {
is Either.Left -> f()
is Either.Right -> this
}
/**
* Turns your Either into an Option.
*/
fun Either.asOption(): Option = when (this) {
is Either.Left -> None
is Either.Right -> Some(this.value)
}
/**
* Turns the left side of your Either into an Option.
*/
fun Either.leftAsOption(): Option = when (this) {
is Either.Left -> Some(this.value)
is Either.Right -> None
}
/**
* Pass the left value to the function (e.g. for logging errors)
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun Either.tapLeft(f: (A) -> Unit): Either =
onLeft { f(it) }
/**
* Performs an effect on the right side of the Either.
*/
inline fun Either.forEach(f: (B) -> Unit): Unit {
onRight { f(it) }
}
/**
* Performs an effect on the left side of the Either.
*/
inline fun Either.leftForEach(f: (A) -> Unit): Unit {
onLeft { f(it) }
}
/**
* Performs an effect over the right side of the value but maps the original value back into
* the Either. This is useful for mixing with validation functions.
*/
inline fun Either.flatTap(f: (B) -> Either): Either = this.flatMap { b ->
f(b).map { b }
}
/**
* Lifts a nullable value into an Either, similar to toOption. Must supply the left side
* of the Either.
*/
inline fun B?.toEither(left: () -> A): Either = this.toOption().toEither { left() }
/**
* Map on a nested Either Option type.
*/
inline fun Either>.mapOption(f: (T) -> V): Either> =
outcome { f(bind().bind()) }.inner
/**
* Map right to Unit. This restores `.void()` which was deprecated by Arrow.
*/
fun Either.unit() = map { }
@PublishedApi
internal val rightUnit: Either = Either.Right(Unit)
inline fun Either.zip(b: Either, transform: (B, C) -> D): Either =
flatMap { a ->
b.map { bb -> transform(a, bb) }
}
inline fun Either.zip(
b: Either,
c: Either,
transform: (B, C, D) -> E,
): Either =
zip(
b,
c,
rightUnit,
rightUnit,
rightUnit,
rightUnit,
rightUnit,
rightUnit,
rightUnit
) { a, bb, cc, _, _, _, _, _, _, _ -> transform(a, bb, cc) }
inline fun Either.zip(
b: Either,
c: Either,
d: Either,
transform: (B, C, D, E) -> F,
): Either =
zip(
b,
c,
d,
rightUnit,
rightUnit,
rightUnit,
rightUnit,
rightUnit,
rightUnit
) { a, bb, cc, dd, _, _, _, _, _, _ -> transform(a, bb, cc, dd) }
inline fun Either.zip(
b: Either,
c: Either,
d: Either,
e: Either,
transform: (B, C, D, E, F) -> G,
): Either =
zip(
b,
c,
d,
e,
rightUnit,
rightUnit,
rightUnit,
rightUnit,
rightUnit
) { a, bb, cc, dd, ee, _, _, _, _, _ -> transform(a, bb, cc, dd, ee) }
inline fun Either.zip(
b: Either,
c: Either,
d: Either,
e: Either,
f: Either,
transform: (B, C, D, E, F, G) -> H,
): Either =
zip(b, c, d, e, f, rightUnit, rightUnit, rightUnit, rightUnit) { a, bb, cc, dd, ee, ff, _, _, _, _ ->
transform(
a,
bb,
cc,
dd,
ee,
ff
)
}
inline fun Either.zip(
b: Either,
c: Either,
d: Either,
e: Either,
f: Either,
g: Either,
transform: (B, C, D, E, F, G, H) -> I,
): Either =
zip(b, c, d, e, f, g, rightUnit, rightUnit, rightUnit) { a, bb, cc, dd, ee, ff, gg, _, _, _ ->
transform(
a,
bb,
cc,
dd,
ee,
ff,
gg
)
}
inline fun Either.zip(
b: Either,
c: Either,
d: Either,
e: Either,
f: Either,
g: Either,
h: Either,
transform: (B, C, D, E, F, G, H, I) -> J,
): Either =
zip(b, c, d, e, f, g, h, rightUnit, rightUnit) { a, bb, cc, dd, ee, ff, gg, hh, _, _ -> transform(a, bb, cc, dd, ee, ff, gg, hh) }
inline fun Either.zip(
b: Either,
c: Either,
d: Either,
e: Either,
f: Either,
g: Either,
h: Either,
i: Either,
transform: (B, C, D, E, F, G, H, I, J) -> K,
): Either =
zip(b, c, d, e, f, g, h, i, rightUnit) { a, bb, cc, dd, ee, ff, gg, hh, ii, _ -> transform(a, bb, cc, dd, ee, ff, gg, hh, ii) }
inline fun Either.zip(
b: Either,
c: Either,
d: Either,
e: Either,
f: Either,
g: Either,
h: Either,
i: Either,
j: Either,
transform: (B, C, D, E, F, G, H, I, J, K) -> L,
): Either =
flatMap { a ->
b.flatMap { bb ->
c.flatMap { cc ->
d.flatMap { dd ->
e.flatMap { ee ->
f.flatMap { ff ->
g.flatMap { gg ->
h.flatMap { hh ->
i.flatMap { ii ->
j.map { jj ->
transform(a, bb, cc, dd, ee, ff, gg, hh, ii, jj)
}
}
}
}
}
}
}
}
}
}
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
@OptIn(ExperimentalTypeInference::class)
@OverloadResolutionByLambdaReturnType
inline fun Either.traverse(transform: (value: A) -> Iterable): List> =
when (this) {
is Either.Left -> listOf(this)
is Either.Right -> transform(value).map { it.right() }
}
fun Either>.sequence(): List> = quiverTraverse(::identity)
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
@OptIn(ExperimentalTypeInference::class)
@OverloadResolutionByLambdaReturnType
inline fun Either.traverse(transform: (value: A) -> Option): Option> =
when (this) {
is Either.Left -> Some(this)
is Either.Right -> transform(value).map { it.right() }
}
/**
* Synonym for traverse((A)-> Option): Option>
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun Either.traverseOption(transform: (A) -> Option): Option> =
quiverTraverse(transform)
fun Either>.sequence(): Option> = quiverTraverse(::identity)
/**
* Recovers the left side of an Either with the supplied function
*/
@OptIn(ExperimentalContracts::class)
inline fun Either.handleErrorWith(f: (A) -> Either): Either {
contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) }
return recover { a -> f(a).bind() }
}