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

app.cash.quiver.raise.OutcomeBuilder.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.raise

import app.cash.quiver.Absent
import app.cash.quiver.Absent.inner
import app.cash.quiver.Outcome
import app.cash.quiver.Present
import app.cash.quiver.extensions.ErrorOr
import app.cash.quiver.extensions.OutcomeOf
import app.cash.quiver.extensions.toOutcomeOf
import app.cash.quiver.failure
import app.cash.quiver.present
import app.cash.quiver.toOutcome
import arrow.core.Option
import arrow.core.Some
import arrow.core.getOrElse
import arrow.core.raise.Raise
import arrow.core.raise.RaiseDSL
import arrow.core.raise.ensure
import arrow.core.raise.ensureNotNull
import arrow.core.raise.fold
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.experimental.ExperimentalTypeInference

/**
 * DSL build on top of Arrow's Raise for [Outcome].
 *
 * Uses `Raise` to provide a slightly optimised builder
 * than nesting `either { option { } }` and not-being able to use `@JvmInline value class`.
 *
 * With context receivers this can be eliminated all together,
 * and `context(Raise, Raise)` or `context(Raise, Raise)` can be used instead.
 */
@OptIn(ExperimentalTypeInference::class)
inline fun  outcome(@BuilderInference block: OutcomeRaise.() -> A): Outcome =
  fold(
    block = { block(OutcomeRaise(this)) },
    recover = { eOrAbsent ->
      @Suppress("UNCHECKED_CAST")
      if (eOrAbsent === Absent) Absent else (eOrAbsent as E).failure()
    },
    transform = { it.present() }
  )

/**
 * Emulation of _context receivers_,
 * when they're released this can be replaced by _context receiver_ based code in Arrow itself.
 *
 * We guarantee that the wrapped `Any?` will only result in `E` or `Absent`.
 * Exposing this as `Raise` gives natural interoperability with `Raise` DSLs (`Either`).
 */
@OptIn(ExperimentalContracts::class)
class OutcomeRaise(private val raise: Raise) : Raise {

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

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

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

  @RaiseDSL
  fun ensure(condition: Boolean): Unit {
    contract { returns() implies condition }
    return raise.ensure(condition) { Absent }
  }

  @RaiseDSL
  fun  Outcome.bind(): A {
    contract { returns() implies (this@bind is Present) }
    return inner.bind().bind()
  }
}

/**
 * DSL build on top of Arrow's Raise for [OutcomeOf].
 *
 * Uses `Raise` to provide a slightly optimised builder
 * than nesting `either { option { } }` and not-being able to use `@JvmInline value class`.
 *
 * With context receivers this can be eliminated all together,
 * and `context(Raise, Raise)` or `context(Raise, Raise)` can be used instead.
 *
 * This is a specialised version and allows interoperability with `Result` as the error side is locked down to
 * `Throwable`.
 */
@OptIn(ExperimentalTypeInference::class)
inline fun  outcomeOf(@BuilderInference block: OutcomeOfRaise.() -> A): OutcomeOf =
  fold(
    block = { block(OutcomeOfRaise(this)) },
    recover = { eOrAbsent ->
      @Suppress("UNCHECKED_CAST")
      if (eOrAbsent === Absent) Absent else (eOrAbsent as Throwable).failure()
    },
    transform = { it.present() }
  )

/**
 * Emulation of _context receivers_,
 * when they're released this can be replaced by _context receiver_ based code in Arrow itself.
 *
 * We guarantee that the wrapped `Any?` will only result in `Throwable` or `Absent`.
 * Exposing this as `Raise` gives natural interoperability with `Raise` DSLs (`Either`).
 */
@OptIn(ExperimentalContracts::class)
class OutcomeOfRaise(private val raise: Raise) : Raise {

  @RaiseDSL
  override fun raise(r: Throwable): Nothing = raise.raise(r)

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

  /**
   * Ensures a nullable value is not null. Will raise Absent on null.
   */
  @RaiseDSL
  fun  A?.bindNull(): A = ensureNotNull(this)

  /**
   * Converts `Result>` to OutcomeOf and binds over the value
   */
  @RaiseDSL
  fun  Result>.bind(): A = toOutcomeOf().bind()

  /**
   * Converts `Result` to OutcomeOf and binds over the value
   */
  @RaiseDSL
  fun  Result.bindResult(): A = toOutcome().bind()

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

  /**
   * Ensures the condition is met and raises an Absent otherwise.
   */
  @RaiseDSL
  fun ensure(condition: Boolean): Unit {
    contract { returns() implies condition }
    return raise.ensure(condition) { Absent }
  }

  @RaiseDSL
  fun  OutcomeOf.bind(): A {
    contract { returns() implies (this@bind is Present) }
    return inner.bind().bind()
  }

  /**
   * Converts an ErrorOr> to an OutcomeOf and binds over the value
   */
  @RaiseDSL
  fun  ErrorOr>.bindOption(): A {
    return toOutcome().bind()
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy