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

app.cash.quiver.extensions.Either.kt Maven / Gradle / Ivy

Go to download

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() }
}