app.cash.quiver.raise.OutcomeBuilder.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lib Show documentation
Show all versions of lib Show documentation
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