Skip to content

Commit

Permalink
Deprecate Ok/Err as return types
Browse files Browse the repository at this point in the history
This is in preparation for the v2 release where these don't exist as types.
  • Loading branch information
michaelbull committed Mar 16, 2024
1 parent b19894a commit 7013a19
Show file tree
Hide file tree
Showing 13 changed files with 532 additions and 419 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.github.michaelbull.result.coroutines

import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.asErr
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
Expand All @@ -12,13 +12,31 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext

/**
* Suspending variant of [binding][com.github.michaelbull.result.binding].
* The suspendable [block] runs in a new [CoroutineScope], inheriting the parent [CoroutineContext].
* This new scope is [cancelled][CoroutineScope.cancel] once a failing bind is encountered, eagerly cancelling all
* child [jobs][Job].
* Calls the specified function [block] with [CoroutineBindingScope] as its receiver and returns
* its [Result].
*
* When inside a binding [block], the [bind][CoroutineBindingScope.bind] function is accessible on
* any [Result]. Calling the [bind][CoroutineBindingScope.bind] function will attempt to unwrap the
* [Result] and locally return its [value][Result.value].
*
* Unlike [binding], this function is designed for _concurrent decomposition_ of work. When any
* [bind][CoroutineBindingScope.bind] returns an error, the [CoroutineScope] will be
* [cancelled][Job.cancel], cancelling all the other children.
*
* This function returns as soon as the given [block] and all its child coroutines are completed.
*
* Example:
* ```
* suspend fun provideX(): Result<Int, ExampleErr> { ... }
* suspend fun provideY(): Result<Int, ExampleErr> { ... }
*
* val result: Result<Int, ExampleErr> = coroutineBinding {
* val x = async { provideX().bind() }
* val y = async { provideY().bind() }
* x.await() + y.await()
* }
*/
public suspend inline fun <V, E> coroutineBinding(crossinline block: suspend CoroutineBindingScope<E>.() -> V): Result<V, E> {
contract {
Expand Down Expand Up @@ -55,11 +73,12 @@ internal class CoroutineBindingScopeImpl<E>(
var result: Result<Nothing, E>? = null

override suspend fun <V> Result<V, E>.bind(): V {
return when (this) {
is Ok -> value
is Err -> mutex.withLock {
return if (isOk) {
value
} else {
mutex.withLock {
if (result == null) {
result = this
result = this.asErr()
coroutineContext.cancel(BindCancellationException)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

/**
* Returns [result] if this [Result] is [Ok], otherwise this [Err].
* Returns [result] if this result [is ok][Result.isOk], otherwise [this].
*
* - Rust: [Result.and](https://doc.rust-lang.org/std/result/enum.Result.html#method.and)
*/
public infix fun <V, E, U> Result<V, E>.and(result: Result<U, E>): Result<U, E> {
return when (this) {
is Ok -> result
is Err -> this
return when {
isOk -> result
else -> this.asErr()
}
}

Expand All @@ -26,7 +26,7 @@ public inline infix fun <V, E, U> Result<V, E>.and(result: () -> Result<U, E>):

/**
* Maps this [Result<V, E>][Result] to [Result<U, E>][Result] by either applying the [transform]
* function if this [Result] is [Ok], or returning this [Err].
* function if this result [is ok][Result.isOk], or returning [this].
*
* - Elm: [Result.andThen](http://package.elm-lang.org/packages/elm-lang/core/latest/Result#andThen)
* - Rust: [Result.and_then](https://doc.rust-lang.org/std/result/enum.Result.html#method.and_then)
Expand All @@ -36,8 +36,8 @@ public inline infix fun <V, E, U> Result<V, E>.andThen(transform: (V) -> Result<
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}

return when (this) {
is Ok -> transform(value)
is Err -> this
return when {
isOk -> transform(value)
else -> this.asErr()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

/**
* Calls the specified function [block] with [BindingScope] as its receiver and returns its [Result].
* Calls the specified function [block] with [BindingScope] as its receiver and returns its
* [Result].
*
* When inside a [binding] block, the [bind][BindingScope.bind] function is accessible on any [Result]. Calling the
* [bind][BindingScope.bind] function will attempt to unwrap the [Result] and locally return its [value][Ok.value]. If
* the [Result] is an [Err], the binding block will terminate with that bind and return that failed-to-bind [Err].
* When inside a binding [block], the [bind][BindingScope.bind] function is accessible on any
* [Result]. Calling the [bind][BindingScope.bind] function will attempt to unwrap the [Result]
* and locally return its [value][Result.value].
*
* If a [bind][BindingScope.bind] returns an error, the [block] will terminate immediately.
*
* Example:
* ```
Expand Down Expand Up @@ -54,12 +57,11 @@ internal class BindingScopeImpl<E> : BindingScope<E> {
var result: Result<Nothing, E>? = null

override fun <V> Result<V, E>.bind(): V {
return when (this) {
is Ok -> value
is Err -> {
this@BindingScopeImpl.result = this
throw BindException
}
return if (isOk) {
value
} else {
result = this.asErr()
throw BindException
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

/**
* Returns the [value][Ok.value] if this [Result] is [Ok], otherwise `null`.
* Returns the [value][Result.value] if this result [is ok][Result.isOk], otherwise `null`.
*
* - Elm: [Result.toMaybe](http://package.elm-lang.org/packages/elm-lang/core/latest/Result#toMaybe)
* - Rust: [Result.ok](https://doc.rust-lang.org/std/result/enum.Result.html#method.ok)
Expand All @@ -15,14 +15,14 @@ public fun <V, E> Result<V, E>.get(): V? {
returns(null) implies (this@get is Err<E>)
}

return when (this) {
is Ok -> value
is Err -> null
return when {
isOk -> value
else -> null
}
}

/**
* Returns the [error][Err.error] if this [Result] is [Err], otherwise `null`.
* Returns the [error][Result.error] if this result [is an error][Result.isErr], otherwise `null`.
*
* - Rust: [Result.err](https://doc.rust-lang.org/std/result/enum.Result.html#method.err)
*/
Expand All @@ -32,26 +32,26 @@ public fun <V, E> Result<V, E>.getError(): E? {
returnsNotNull() implies (this@getError is Err<E>)
}

return when (this) {
is Ok -> null
is Err -> error
return when {
isErr -> error
else -> null
}
}

/**
* Returns the [value][Ok.value] if this [Result] is [Ok], otherwise [default].
* Returns the [value][Result.value] if this result [is ok][Result.isOk], otherwise [default].
*
* - Elm: [Result.withDefault](http://package.elm-lang.org/packages/elm-lang/core/latest/Result#withDefault)
* - Haskell: [Result.fromLeft](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Either.html#v:fromLeft)
* - Rust: [Result.unwrap_or](https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_or)
*
* @param default The value to return if [Err].
* @return The [value][Ok.value] if [Ok], otherwise [default].
* @return The [value][Result.value] if [Ok], otherwise [default].
*/
public infix fun <V, E> Result<V, E>.getOr(default: V): V {
return when (this) {
is Ok -> value
is Err -> default
return when {
isOk -> value
else -> default
}
}

Expand All @@ -65,17 +65,18 @@ public inline infix fun <V, E> Result<V, E>.getOr(default: () -> V): V {
}

/**
* Returns the [error][Err.error] if this [Result] is [Err], otherwise [default].
* Returns the [error][Result.error] if this result [is an error][Result.isErr], otherwise
* [default].
*
* - Haskell: [Result.fromRight](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Either.html#v:fromRight)
*
* @param default The error to return if [Ok].
* @return The [error][Err.error] if [Err], otherwise [default].
* @return The [error][Result.error] if [Err], otherwise [default].
*/
public infix fun <V, E> Result<V, E>.getErrorOr(default: E): E {
return when (this) {
is Ok -> default
is Err -> error
return when {
isOk -> default
else -> error
}
}

Expand All @@ -89,8 +90,8 @@ public inline infix fun <V, E> Result<V, E>.getErrorOr(default: () -> E): E {
}

/**
* Returns the [value][Ok.value] if this [Result] is [Ok], otherwise the
* [transformation][transform] of the [error][Err.error].
* Returns the [value][Result.value] if this result [is ok][Result.isOk], otherwise the
* [transformation][transform] of the [error][Result.error].
*
* - Elm: [Result.extract](http://package.elm-lang.org/packages/elm-community/result-extra/2.2.0/Result-Extra#extract)
* - Rust: [Result.unwrap_or_else](https://doc.rust-lang.org/src/core/result.rs.html#735-740)
Expand All @@ -100,30 +101,30 @@ public inline infix fun <V, E> Result<V, E>.getOrElse(transform: (E) -> V): V {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}

return when (this) {
is Ok -> value
is Err -> transform(error)
return when {
isOk -> value
else -> transform(error)
}
}

/**
* Returns the [error][Err.error] if this [Result] is [Err], otherwise the
* [transformation][transform] of the [value][Ok.value].
* Returns the [error][Result.error] if this result [is an error][Result.isErr], otherwise the
* [transformation][transform] of the [value][Result.value].
*/
public inline infix fun <V, E> Result<V, E>.getErrorOrElse(transform: (V) -> E): E {
contract {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}

return when (this) {
is Ok -> transform(value)
is Err -> error
return when {
isErr -> error
else -> transform(value)
}
}

/**
* Returns the [value][Ok.value] if this [Result] is [Ok], otherwise throws the
* [error][Err.error].
* Returns the [value][Result.value] if this result [is ok][Result.isOk], otherwise throws the
* [error][Result.error].
*
* This is functionally equivalent to [`getOrElse { throw it }`][getOrElse].
*/
Expand All @@ -132,37 +133,37 @@ public fun <V, E : Throwable> Result<V, E>.getOrThrow(): V {
returns() implies (this@getOrThrow is Ok<V>)
}

return when (this) {
is Ok -> value
is Err -> throw error
return when {
isOk -> value
else -> throw error
}
}

/**
* Returns the [value][Ok.value] if this [Result] is [Ok], otherwise throws the
* [transformation][transform] of the [error][Err.error] to a [Throwable].
* Returns the [value][Result.value] if this result [is ok][Result.isOk], otherwise throws the
* [transformation][transform] of the [error][Result.error] to a [Throwable].
*/
public inline infix fun <V, E> Result<V, E>.getOrThrow(transform: (E) -> Throwable): V {
contract {
returns() implies (this@getOrThrow is Ok<V>)
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}

return when (this) {
is Ok -> value
is Err -> throw transform(error)
return when {
isOk -> value
else -> throw transform(error)
}
}

/**
* Merges this [Result<V, E>][Result] to [U], returning the [value][Ok.value] if this [Result] is [Ok], otherwise the
* [error][Err.error].
* Merges this [Result<V, E>][Result] to [U], returning the [value][Result.value] if this result
* [is ok][Result.isOk], otherwise the [error][Result.error].
*
* - Scala: [MergeableEither.merge](https://www.scala-lang.org/api/2.12.0/scala/util/Either$$MergeableEither.html#merge:A)
*/
public fun <V : U, E : U, U> Result<V, E>.merge(): U {
return when (this) {
is Ok -> value
is Err -> error
return when {
isOk -> value
else -> error
}
}
Loading

0 comments on commit 7013a19

Please sign in to comment.