All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.arrow.core.Ior.kt Maven / Gradle / Ivy

There is a newer version: 2.0.1
Show newest version
@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) }
    )
  }
)