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

commonMain.arrow.core.raise.Builders.kt Maven / Gradle / Ivy

There is a newer version: 2.0.1
Show newest version
@file:JvmMultifileClass
@file:JvmName("RaiseKt")
@file:OptIn(ExperimentalTypeInference::class, ExperimentalContracts::class)

package arrow.core.raise

import arrow.atomic.Atomic
import arrow.atomic.updateAndGet
import arrow.core.Either
import arrow.core.EmptyValue
import arrow.core.Ior
import arrow.core.IorNel
import arrow.core.NonEmptyList
import arrow.core.NonEmptySet
import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.getOrElse
import arrow.core.identity
import arrow.core.none
import arrow.core.some
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.experimental.ExperimentalTypeInference
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName

@RaiseDSL
public inline fun  singleton(
  raise: () -> A,
  @BuilderInference block: SingletonRaise.() -> A,
): A {
  contract {
    callsInPlace(raise, InvocationKind.AT_MOST_ONCE)
    callsInPlace(block, InvocationKind.AT_MOST_ONCE)
  }
  return recover({ block(SingletonRaise(this)) }) { raise() }
}

/**
 * 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  either(@BuilderInference block: Raise.() -> A): Either {
  contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) }
  return fold(block, { 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).
 *
 * @see SingletonRaise.ignoreErrors By default, `nullable` only allows raising `null`.
 * Calling [ignoreErrors][SingletonRaise.ignoreErrors] inside `nullable` allows to raise any error, which will be returned to the caller as if `null` was raised.
 */
public inline fun  nullable(block: SingletonRaise.() -> A): A? {
  contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) }
  return singleton({ null }, block)
}

/**
 * Runs a computation [block] using [Raise], and return its outcome as [Result].
 *
 *
 * 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  result(block: ResultRaise.() -> A): Result {
  contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) }
  return 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  option(block: SingletonRaise.() -> A): Option {
  contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) }
  return singleton(::none) { block().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  ior(noinline combineError: (Error, Error) -> Error, @BuilderInference block: IorRaise.() -> A): Ior {
  contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) }
  val state: Atomic = Atomic(EmptyValue)
  return fold(
    { block(IorRaise(combineError, state, this)) },
    { e -> Ior.Left(EmptyValue.combine(state.get(), e, combineError)) },
    { a -> EmptyValue.fold(state.get(), { Ior.Right(a) }, { e: Error -> Ior.Both(e, a) }) }
  )
}

/**
 * Run a computation [block] using [Raise]. and return its outcome as [IorNel].
 * - [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]. This defaults to
 * combining [NonEmptyList]s by concatenating them.
 *
 * 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  iorNel(noinline combineError: (NonEmptyList, NonEmptyList) -> NonEmptyList = { a, b -> a + b }, @BuilderInference block: IorRaise>.() -> A): IorNel {
  contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) }
  return ior(combineError, block)
}

/**
 * Runs a computation [block] using [Raise], and ignore its outcome.
 *
 * 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 impure(block: SingletonRaise.() -> Unit) {
  contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) }
  return singleton({ }, block)
}

public class SingletonRaise(private val raise: Raise): Raise {
  @RaiseDSL
  public fun raise(): Nothing = raise.raise(Unit)

  @RaiseDSL
  override fun raise(r: E): Nothing = raise()

  @RaiseDSL
  public fun ensure(condition: Boolean) {
    contract { returns() implies condition }
    return if (condition) Unit else raise()
  }

  @RaiseDSL
  public fun  Option.bind(): A {
    contract { returns() implies (this@bind is Some) }
    return getOrElse { raise() }
  }

  @RaiseDSL
  public fun  A?.bind(): A {
    contract { returns() implies (this@bind != null) }
    return this ?: raise()
  }

  @RaiseDSL
  public fun  ensureNotNull(value: A?): A {
    contract { returns() implies (value != null) }
    return value ?: raise()
  }

  @RaiseDSL
  @JvmName("bindAllNullable")
  public fun  Map.bindAll(): Map =
    mapValues { (_, v) -> v.bind() }

  @JvmName("bindAllOption")
  public fun  Map>.bindAll(): Map =
    mapValues { (_, v) -> v.bind() }

  @RaiseDSL
  @JvmName("bindAllNullable")
  public fun  Iterable.bindAll(): List =
    map { it.bind() }

  @RaiseDSL
  @JvmName("bindAllOption")
  public fun  Iterable>.bindAll(): List =
    map { it.bind() }

  @RaiseDSL
  @JvmName("bindAllNullable")
  public fun  NonEmptyList.bindAll(): NonEmptyList =
    map { it.bind() }

  @RaiseDSL
  @JvmName("bindAllOption")
  public fun  NonEmptyList>.bindAll(): NonEmptyList =
    map { it.bind() }

  @RaiseDSL
  @JvmName("bindAllNullable")
  public fun  NonEmptySet.bindAll(): NonEmptySet =
    map { it.bind() }.toNonEmptySet()

  @RaiseDSL
  @JvmName("bindAllOption")
  public fun  NonEmptySet>.bindAll(): NonEmptySet =
    map { it.bind() }.toNonEmptySet()

  @RaiseDSL
  public inline fun  recover(
    block: SingletonRaise.() -> A,
    raise: () -> A,
  ): A {
    contract {
      callsInPlace(block, InvocationKind.AT_MOST_ONCE)
      callsInPlace(raise, InvocationKind.AT_MOST_ONCE)
    }
    return recover<_, A>({ block(SingletonRaise(this)) }) { raise() }
  }

  /**
   * Introduces a scope where you can [bind] errors of any type,
   * but no information is saved in the [raise] case.
   */
  @RaiseDSL
  public inline fun  ignoreErrors(
    block: SingletonRaise.() -> A,
  ): A {
    contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
    // This is safe because SingletonRaise never leaks the e from `raise(e: E)`, instead always calling `raise()`.
    // and hence the type parameter of SingletonRaise merely states what errors it accepts and ignores.
    @Suppress("UNCHECKED_CAST")
    return block(this as SingletonRaise)
  }
}

/**
 * Implementation of [Raise] used by [result].
 * You should never use this directly.
 */
public class ResultRaise(private val raise: Raise) : Raise by raise {
  @RaiseDSL
  public fun  Result.bind(): A = fold(::identity) { raise(it) }

  @JvmName("bindAllResult")
  public fun  Map>.bindAll(): Map =
    mapValues { (_, v) -> v.bind() }

  @RaiseDSL
  @JvmName("bindAllResult")
  public fun  Iterable>.bindAll(): List =
    map { it.bind() }

  @RaiseDSL
  @JvmName("bindAllResult")
  public fun  NonEmptyList>.bindAll(): NonEmptyList =
    map { it.bind() }

  @RaiseDSL
  @JvmName("bindAllResult")
  public fun  NonEmptySet>.bindAll(): NonEmptySet =
    map { it.bind() }.toNonEmptySet()

  @RaiseDSL
  public inline fun  recover(
    @BuilderInference block: ResultRaise.() -> A,
    recover: (Throwable) -> A,
  ): A {
    contract {
      callsInPlace(block, InvocationKind.AT_MOST_ONCE)
      callsInPlace(recover, InvocationKind.AT_MOST_ONCE)
    }
    return result(block).fold(
      onSuccess = { it },
      onFailure = { recover(it) }
    )
  }
}

/**
 * Implementation of [Raise] used by [ior].
 * You should never use this directly.
 */
public class IorRaise @PublishedApi internal constructor(
  @PublishedApi internal val combineError: (Error, Error) -> Error,
  private val state: Atomic,
  private val raise: Raise,
) : Raise by raise {
  @Suppress("UNCHECKED_CAST")
  @PublishedApi
  internal fun combine(e: Error): Error = state.updateAndGet { EmptyValue.combine(it, e, combineError) } as Error

  @RaiseDSL
  public fun accumulate(value: Error): Unit = Ior.Both(value, Unit).bind()

  @RaiseDSL
  public fun  Either.getOrAccumulate(recover: (Error) -> A): A =
    fold(ifLeft = { Ior.Both(it, recover(it)) }, ifRight = { Ior.Right(it) }).bind()

  @RaiseDSL
  @JvmName("bindAllIor")
  public fun  Iterable>.bindAll(): List =
    map { it.bind() }

  @RaiseDSL
  @JvmName("bindAllIor")
  public fun  NonEmptyList>.bindAll(): NonEmptyList =
    map { it.bind() }

  @RaiseDSL
  @JvmName("bindAllIor")
  public fun  NonEmptySet>.bindAll(): NonEmptySet =
    map { it.bind() }.toNonEmptySet()

  @RaiseDSL
  public fun  Ior.bind(): A =
    when (this) {
      is Ior.Left -> raise(value)
      is Ior.Right -> value
      is Ior.Both -> {
        combine(leftValue)
        rightValue
      }
    }

  @JvmName("bindAllIor")
  public fun  Map>.bindAll(): Map =
    mapValues { (_, v) -> v.bind() }

  @RaiseDSL
  public inline fun  recover(
    @BuilderInference block: IorRaise.() -> A,
    recover: (error: Error) -> A,
  ): A {
    contract {
      callsInPlace(block, InvocationKind.AT_MOST_ONCE)
      callsInPlace(recover, InvocationKind.AT_MOST_ONCE)
    }
    val state: Atomic = Atomic(EmptyValue)
    return recover({
      try {
        block(IorRaise(combineError, state, this))
      } finally {
        val accumulated = state.get()
        if (accumulated != EmptyValue) {
          @Suppress("UNCHECKED_CAST")
          combine(accumulated as Error)
        }
      }
    }, recover)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy