
commonMain.arrow.core.Ior.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of arrow-core Show documentation
Show all versions of arrow-core Show documentation
Functional companion to Kotlin's Standard Library
@file:OptIn(ExperimentalContracts::class)
package arrow.core
import arrow.core.Ior.Both
import arrow.core.Ior.Left
import arrow.core.Ior.Right
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.jvm.JvmStatic
public typealias IorNel = Ior, B>
/**
* Port of https://github.com/typelevel/cats/blob/v0.9.0/core/src/main/scala/cats/data/Ior.scala
*
* Represents a right-biased disjunction that is either an `A`, or a `B`, or both an `A` and a `B`.
*
* An instance of [Ior]<`A`,`B`> is one of:
* - [Ior.Left] <`A`>
* - [Ior.Right] <`B`>
* - [Ior.Both]<`A`,`B`>
*
* [Ior]<`A`,`B`> is similar to [Either]<`A`,`B`>, except that it can represent the simultaneous presence of
* an `A` and a `B`. It is right-biased so methods such as `map` and `flatMap` operate on the
* `B` value. Some methods, like `flatMap`, handle the presence of two [Ior.Both] values using a
* `combine` function `(A, A) -> A`, while other methods, like [toEither], ignore the `A` value in a [Ior.Both Both].
*
* [Ior]<`A`,`B`> is isomorphic to [Either]<[Either]<`A`,`B`>, [Pair]<`A`,`B`>>, but provides methods biased toward `B`
* values, regardless of whether the `B` values appear in a [Ior.Right] or a [Ior.Both].
* The isomorphic Either form can be accessed via the [unwrap] method.
*/
public sealed class Ior {
/**
* Returns `true` if this is a [Left], `false` otherwise.
*
* Example:
*
* ```kotlin
* import arrow.core.Ior
*
* fun main() {
* Ior.Left("tulip").isLeft() // Result: true
* Ior.Right("venus fly-trap").isLeft() // Result: false
* Ior.Both("venus", "fly-trap").isLeft() // Result: false
* }
* ```
*
*/
public fun isLeft(): Boolean {
contract {
returns(true) implies (this@Ior is Ior.Left)
returns(false) implies (this@Ior is Ior.Right || this@Ior is Ior.Both)
}
return this@Ior is Ior.Left
}
/**
* Returns `true` if this is a [Right], `false` otherwise.
*
* Example:
*
* ```kotlin
* import arrow.core.Ior
*
* fun main() {
* Ior.Left("tulip").isRight() // Result: false
* Ior.Right("venus fly-trap").isRight() // Result: true
* Ior.Both("venus", "fly-trap").isRight() // Result: false
* }
* ```
*
*/
public fun isRight(): Boolean {
contract {
returns(true) implies (this@Ior is Ior.Right)
returns(false) implies (this@Ior is Ior.Left || this@Ior is Ior.Both)
}
return this@Ior is Ior.Right
}
/**
* Returns `true` if this is a [Both], `false` otherwise.
*
* Example:
* ```kotlin
* import arrow.core.Ior
*
* fun main() {
* Ior.Left("tulip").isBoth() // Result: false
* Ior.Right("venus fly-trap").isBoth() // Result: false
* Ior.Both("venus", "fly-trap").isBoth() // Result: true
* }
* ```
*
*/
public fun isBoth(): Boolean {
contract {
returns(false) implies (this@Ior is Ior.Right || this@Ior is Ior.Left)
returns(true) implies (this@Ior is Ior.Both)
}
return this@Ior is Ior.Both
}
public companion object {
/**
* Create an [Ior] from two nullables if at least one of them is defined.
*
* @param a an element (nullable) for the left side of the [Ior]
* @param b an element (nullable) for the right side of the [Ior]
*
* @return `null` if both [a] and [b] are `null`. Otherwise
* an [Ior.Left], [Ior.Right], or [Ior.Both] if [a], [b], or both are defined (respectively).
*/
@JvmStatic
public fun fromNullables(a: A?, b: B?): Ior? =
when (a != null) {
true -> when (b != null) {
true -> Both(a, b)
false -> Left(a)
}
false -> when (b != null) {
true -> Right(b)
false -> null
}
}
@JvmStatic
public fun leftNel(a: A): IorNel = Left(nonEmptyListOf(a))
@JvmStatic
public fun bothNel(a: A, b: B): IorNel = Both(nonEmptyListOf(a), b)
}
/**
* Applies `fa` if this is a [Left], `fb` if this is a [Right] or `fab` if this is a [Both]
*
*
* @param fa the function to apply if this is a [Left]
* @param fb the function to apply if this is a [Right]
* @param fab the function to apply if this is a [Both]
* @return the results of applying the function
*/
public inline fun fold(fa: (A) -> C, fb: (B) -> C, fab: (A, B) -> C): C {
contract {
callsInPlace(fa, InvocationKind.AT_MOST_ONCE)
callsInPlace(fb, InvocationKind.AT_MOST_ONCE)
callsInPlace(fab, InvocationKind.AT_MOST_ONCE)
}
return when (this) {
is Left -> fa(value)
is Right -> fb(value)
is Both -> fab(leftValue, rightValue)
}
}
/**
* The given function is applied if this is a [Right] or [Both] to `B`.
*
* Example:
* ```kotlin
* import arrow.core.Ior
*
* fun main() {
* Ior.Right(12).map { "flower" } // Result: Right("flower")
* Ior.Left(12).map { "flower" } // Result: Left(12)
* Ior.Both(12, "power").map { "flower $it" } // Result: Both(12, "flower power")
* }
* ```
*
*/
public inline fun map(f: (B) -> D): Ior {
contract { callsInPlace(f, InvocationKind.AT_MOST_ONCE) }
return when (this) {
is Left -> this
is Right -> Right(f(value))
is Both -> Both(leftValue, f(rightValue))
}
}
/**
* The given function is applied if this is a [Left] or [Both] to `A`.
*
* Example:
* ```kotlin
* import arrow.core.Ior
*
* fun main() {
* Ior.Right(12).mapLeft { "flower" } // Result: Right(12)
* Ior.Left(12).mapLeft { "flower" } // Result: Left("power")
* Ior.Both(12, "power").mapLeft { "flower $it" } // Result: Both("flower 12", "power")
* }
* ```
*
*/
public inline fun mapLeft(fa: (A) -> C): Ior {
contract { callsInPlace(fa, InvocationKind.AT_MOST_ONCE) }
return when (this) {
is Left -> Left(fa(value))
is Right -> this
is Both -> Both(fa(leftValue), rightValue)
}
}
/**
* If this is a [Left], then return the left value in [Right] or vice versa,
* when this is [Both] , left and right values are swap
*
* Example:
* ```kotlin
* import arrow.core.Ior
*
* fun main() {
* Ior.Left("left").swap() // Result: Right("left")
* Ior.Right("right").swap() // Result: Left("right")
* Ior.Both("left", "right").swap() // Result: Both("right", "left")
* }
* ```
*
*/
public fun swap(): Ior = fold(
{ Right(it) },
{ Left(it) },
{ a, b -> Both(b, a) }
)
/**
* Return the isomorphic [Either] of this [Ior]
*/
public fun unwrap(): Either, Pair> = fold(
{ Either.Left(Either.Left(it)) },
{ Either.Left(Either.Right(it)) },
{ a, b -> Either.Right(Pair(a, b)) }
)
public fun toPair(): Pair = fold(
{ Pair(it, null) },
{ Pair(null, it) },
{ a, b -> Pair(a, b) }
)
/**
* Returns a [Either.Right] containing the [Right] value or `B` if this is [Right] or [Both]
* and [Either.Left] if this is a [Left].
*
* Example:
* ```kotlin
* import arrow.core.Ior
*
* fun main() {
* Ior.Right(12).toEither() // Result: Either.Right(12)
* Ior.Left(12).toEither() // Result: Either.Left(12)
* Ior.Both("power", 12).toEither() // Result: Either.Right(12)
* }
* ```
*
*/
public fun toEither(): Either =
fold({ Either.Left(it) }, { Either.Right(it) }, { _, b -> Either.Right(b) })
public fun getOrNull(): B? {
contract {
returns(null) implies (this@Ior is Left)
returnsNotNull() implies ((this@Ior is Right) || (this@Ior is Both))
}
return fold({ null }, { it }, { _, b -> b })
}
/**
* Returns the [Left] value or `A` if this is [Left] or [Both]
* and `null` if this is a [Right].
*
* Example:
* ```kotlin
* import arrow.core.Ior
*
* fun main() {
* val right = Ior.Right(12).leftOrNull() // Result: null
* val left = Ior.Left(12).leftOrNull() // Result: 12
* val both = Ior.Both(12, "power").leftOrNull() // Result: 12
* println("right = $right")
* println("left = $left")
* println("both = $both")
* }
* ```
*
*/
public fun leftOrNull(): A? {
contract {
returns(null) implies (this@Ior is Right)
returnsNotNull() implies ((this@Ior is Left) || (this@Ior is Both))
}
return fold({ it }, { null }, { a, _ -> a })
}
public data class Left(val value: A) : Ior() {
override fun toString(): String = "Ior.Left($value)"
public companion object
}
public data class Right(val value: B) : Ior() {
override fun toString(): String = "Ior.Right($value)"
public companion object
}
public data class Both(val leftValue: A, val rightValue: B) : Ior() {
override fun toString(): String = "Ior.Both($leftValue, $rightValue)"
}
override fun toString(): String = fold(
{ "Ior.Left($it" },
{ "Ior.Right($it)" },
{ a, b -> "Ior.Both($a, $b)" }
)
/**
* Returns `false` if [Right] or [Both], or returns the result of the application of
* the given predicate to the [Left] value.
*
* Example:
* ```kotlin
* import arrow.core.Ior
*
* fun main() {
* val right: Ior = Ior.Right(12)
* right.isLeft { it > 10 } // Result: false
* Ior.Both(12, 7).isLeft { it > 10 } // Result: false
* Ior.Left(12).isLeft { it > 10 } // Result: true
* }
* ```
*
*/
public inline fun isLeft(predicate: (A) -> Boolean): Boolean {
contract {
returns(true) implies (this@Ior is Left)
returns(false) implies (this@Ior is Right || this@Ior is Both)
callsInPlace(predicate, InvocationKind.AT_MOST_ONCE)
}
return this@Ior is Left && predicate(value)
}
/**
* Returns `false` if [Left] or [Both], or returns the result of the application of
* the given predicate to the [Right] value.
*
* Example:
* ```kotlin
* import arrow.core.Ior
*
* fun main() {
* Ior.Right(12).isRight { it > 10 } // Result: false
* Ior.Both(12, 7).isRight { it > 10 } // Result: false
* val left: Ior = Ior.Left(12)
* left.isRight { it > 10 } // Result: true
* }
* ```
*
*/
public inline fun isRight(predicate: (B) -> Boolean): Boolean {
contract {
returns(true) implies (this@Ior is Right)
returns(false) implies (this@Ior is Left || this@Ior is Both)
callsInPlace(predicate, InvocationKind.AT_MOST_ONCE)
}
return this@Ior is Right && predicate(value)
}
/**
* Returns `false` if [Right] or [Left], or returns the result of the application of
* the given predicate to the [Both] value.
*
* Example:
* ```kotlin
* import arrow.core.Ior
*
* fun main() {
* val right: Ior = Ior.Right(12)
* right.isBoth( {it > 10}, {it > 6 }) // Result: false
* Ior.Both(12, 7).isBoth( {it > 10}, {it > 6 })// Result: true
* val left: Ior = Ior.Left(12)
* left.isBoth ( {it > 10}, {it > 6 }) // Result: false
* }
* ```
*
*/
public inline fun isBoth(leftPredicate: (A) -> Boolean, rightPredicate: (B) -> Boolean): Boolean {
contract {
returns(true) implies (this@Ior is Both)
returns(false) implies (this@Ior is Left || this@Ior is Right)
callsInPlace(leftPredicate, InvocationKind.AT_MOST_ONCE)
callsInPlace(rightPredicate, InvocationKind.AT_MOST_ONCE)
}
return this@Ior is Both && leftPredicate(leftValue) && rightPredicate(rightValue)
}
}
/**
* Binds the given function across [Ior.Right].
*
* @param f The function to bind across [Ior.Right].
*/
public inline fun Ior.flatMap(combine: (A, A) -> A, f: (B) -> Ior): Ior {
contract {
callsInPlace(combine, InvocationKind.AT_MOST_ONCE)
callsInPlace(f, InvocationKind.AT_MOST_ONCE)
}
return when (this) {
is Left -> this
is Right -> f(value)
is Both -> when (val r = f(rightValue)) {
is Left -> Left(combine(this.leftValue, r.value))
is Right -> Both(this.leftValue, r.value)
is Both -> Both(combine(this.leftValue, r.leftValue), r.rightValue)
}
}
}
/**
* Binds the given function across [Ior.Left].
*
* @param f The function to bind across [Ior.Left].
*/
public inline fun Ior.handleErrorWith(combine: (B, B) -> B, f: (A) -> Ior): Ior {
contract {
callsInPlace(combine, InvocationKind.AT_MOST_ONCE)
callsInPlace(f, InvocationKind.AT_MOST_ONCE)
}
return when (this) {
is Left -> f(value)
is Right -> this
is Both -> when (val l = f(leftValue)) {
is Left -> Both(l.value, this.rightValue)
is Right -> Right(combine(this.rightValue, l.value))
is Both -> Both(l.leftValue, combine(this.rightValue, l.rightValue))
}
}
}
public inline fun Ior.getOrElse(default: (A) -> B): B {
contract { callsInPlace(default, InvocationKind.AT_MOST_ONCE) }
return when (this) {
is Left -> default(this.value)
is Right -> this.value
is Both -> this.rightValue
}
}
public fun Pair.bothIor(): Ior = Ior.Both(this.first, this.second)
public fun A.leftIor(): Ior = Ior.Left(this)
public fun A.rightIor(): Ior = Ior.Right(this)
public inline fun Ior.combine(other: Ior, combineA: (A, A) -> A, combineB: (B, B) -> B): Ior {
contract {
callsInPlace(combineA, InvocationKind.AT_MOST_ONCE)
callsInPlace(combineB, InvocationKind.AT_MOST_ONCE)
}
return when (this) {
is Ior.Left -> when (other) {
is Ior.Left -> Ior.Left(combineA(value, other.value))
is Ior.Right -> Ior.Both(value, other.value)
is Ior.Both -> Ior.Both(combineA(value, other.leftValue), other.rightValue)
}
is Ior.Right -> when (other) {
is Ior.Left -> Ior.Both(other.value, value)
is Ior.Right -> Ior.Right(combineB(value, other.value))
is Ior.Both -> Ior.Both(other.leftValue, combineB(value, other.rightValue))
}
is Ior.Both -> when (other) {
is Ior.Left -> Ior.Both(combineA(leftValue, other.value), rightValue)
is Ior.Right -> Ior.Both(leftValue, combineB(rightValue, other.value))
is Ior.Both -> Ior.Both(combineA(leftValue, other.leftValue), combineB(rightValue, other.rightValue))
}
}
}
public inline fun Ior>.flatten(combine: (A, A) -> A): Ior {
contract { callsInPlace(combine, InvocationKind.AT_MOST_ONCE) }
return flatMap(combine, ::identity)
}
/**
* Given an [Ior] with an error type [A], returns an [IorNel] with the same
* error type. Wraps the original error in a [NonEmptyList] so that it can be
* combined with an [IorNel] in a Raise DSL which operates on one.
*/
public fun Ior.toIorNel(): IorNel =
mapLeft { it.nel() }
public operator fun , B : Comparable> Ior.compareTo(other: Ior): Int = fold(
{ a1 -> other.fold({ a2 -> a1.compareTo(a2) }, { -1 }, { _, _ -> -1 }) },
{ b1 -> other.fold({ 1 }, { b2 -> b1.compareTo(b2) }, { _, _ -> -1 }) },
{ a1, b1 ->
other.fold(
{ 1 },
{ 1 },
{ a2, b2 -> if (a1.compareTo(a2) == 0) b1.compareTo(b2) else a1.compareTo(a2) }
)
}
)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy