diff --git a/kotlin-result-coroutines/src/commonMain/kotlin/com/github/michaelbull/result/coroutines/CoroutineBinding.kt b/kotlin-result-coroutines/src/commonMain/kotlin/com/github/michaelbull/result/coroutines/CoroutineBinding.kt index 71ebfa9..bd3b4bf 100644 --- a/kotlin-result-coroutines/src/commonMain/kotlin/com/github/michaelbull/result/coroutines/CoroutineBinding.kt +++ b/kotlin-result-coroutines/src/commonMain/kotlin/com/github/michaelbull/result/coroutines/CoroutineBinding.kt @@ -1,8 +1,9 @@ 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 com.github.michaelbull.result.binding import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -12,13 +13,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 { ... } + * suspend fun provideY(): Result { ... } + * + * val result: Result = coroutineBinding { + * val x = async { provideX().bind() } + * val y = async { provideY().bind() } + * x.await() + y.await() + * } */ public suspend inline fun coroutineBinding(crossinline block: suspend CoroutineBindingScope.() -> V): Result { contract { @@ -55,11 +74,12 @@ internal class CoroutineBindingScopeImpl( var result: Result? = null override suspend fun Result.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) } diff --git a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/And.kt b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/And.kt index 23efe49..1acfb30 100644 --- a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/And.kt +++ b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/And.kt @@ -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 Result.and(result: Result): Result { - return when (this) { - is Ok -> result - is Err -> this + return when { + isOk -> result + else -> this.asErr() } } @@ -26,7 +26,7 @@ public inline infix fun Result.and(result: () -> Result): /** * Maps this [Result][Result] to [Result][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) @@ -36,8 +36,8 @@ public inline infix fun Result.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() } } diff --git a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Binding.kt b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Binding.kt index d723656..5202043 100644 --- a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Binding.kt +++ b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Binding.kt @@ -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: * ``` @@ -54,12 +57,11 @@ internal class BindingScopeImpl : BindingScope { var result: Result? = null override fun Result.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 } } } diff --git a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Get.kt b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Get.kt index ecdb26b..f9a395b 100644 --- a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Get.kt +++ b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Get.kt @@ -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) @@ -15,14 +15,14 @@ public fun Result.get(): V? { returns(null) implies (this@get is Err) } - 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) */ @@ -32,26 +32,26 @@ public fun Result.getError(): E? { returnsNotNull() implies (this@getError is Err) } - 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 Result.getOr(default: V): V { - return when (this) { - is Ok -> value - is Err -> default + return when { + isOk -> value + else -> default } } @@ -65,17 +65,18 @@ public inline infix fun Result.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 Result.getErrorOr(default: E): E { - return when (this) { - is Ok -> default - is Err -> error + return when { + isOk -> default + else -> error } } @@ -89,8 +90,8 @@ public inline infix fun Result.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) @@ -100,30 +101,30 @@ public inline infix fun Result.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 Result.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]. */ @@ -132,15 +133,15 @@ public fun Result.getOrThrow(): V { returns() implies (this@getOrThrow is Ok) } - 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 Result.getOrThrow(transform: (E) -> Throwable): V { contract { @@ -148,21 +149,21 @@ public inline infix fun Result.getOrThrow(transform: (E) -> Throwab 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][Result] to [U], returning the [value][Ok.value] if this [Result] is [Ok], otherwise the - * [error][Err.error]. + * Merges this [Result][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 Result.merge(): U { - return when (this) { - is Ok -> value - is Err -> error + return when { + isOk -> value + else -> error } } diff --git a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Iterable.kt b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Iterable.kt index c68d950..3ac2ba0 100644 --- a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Iterable.kt +++ b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Iterable.kt @@ -1,25 +1,26 @@ package com.github.michaelbull.result /** - * Returns a list containing only elements that are [Ok]. + * Returns a list containing only elements that [are ok][Result.isOk]. */ public fun Iterable>.filterValues(): List { return filterValuesTo(ArrayList()) } /** - * Returns a list containing only elements that are [Err]. + * Returns a list containing only elements that [are an error][Result.isErr]. */ public fun Iterable>.filterErrors(): List { return filterErrorsTo(ArrayList()) } /** - * Appends the [values][Ok.value] of each element that is [Ok] to the given [destination]. + * Appends the [values][Result.value] of each element that [is ok][Result.isOk] to the given + * [destination]. */ public fun > Iterable>.filterValuesTo(destination: C): C { for (element in this) { - if (element is Ok) { + if (element.isOk) { destination.add(element.value) } } @@ -28,11 +29,12 @@ public fun > Iterable>.filterValu } /** - * Appends the [errors][Err.error] of each element that is [Err] to the given [destination]. + * Appends the [errors][Result.error] of each element that [is an error][Result.isErr] to the given + * [destination]. */ public fun > Iterable>.filterErrorsTo(destination: C): C { for (element in this) { - if (element is Err) { + if (element.isErr) { destination.add(element.error) } } @@ -41,45 +43,45 @@ public fun > Iterable>.filterErro } /** - * Returns `true` if each element is [Ok], `false` otherwise. + * Returns `true` if each element [is ok][Result.isOk], `false` otherwise. */ public fun Iterable>.allOk(): Boolean { - return all { it is Ok } + return all(Result::isOk) } /** - * Returns `true` if each element is [Err], `false` otherwise. + * Returns `true` if each element [is an error][Result.isErr], `false` otherwise. */ public fun Iterable>.allErr(): Boolean { - return all { it is Err } + return all(Result::isErr) } /** - * Returns `true` if at least one element is [Ok], `false` otherwise. + * Returns `true` if at least one element [is ok][Result.isOk], `false` otherwise. */ public fun Iterable>.anyOk(): Boolean { - return any { it is Ok } + return any(Result::isOk) } /** - * Returns `true` if at least one element is [Err], `false` otherwise. + * Returns `true` if at least one element [is an error][Result.isErr], `false` otherwise. */ public fun Iterable>.anyErr(): Boolean { - return any { it is Err } + return any(Result::isErr) } /** - * Returns the number of elements that are [Ok]. + * Returns the number of elements that [are ok][Result.isOk]. */ public fun Iterable>.countOk(): Int { - return count { it is Ok } + return count(Result::isOk) } /** - * Returns the number of elements that are [Err]. + * Returns the number of elements that [are an error][Result.isErr]. */ public fun Iterable>.countErr(): Int { - return count { it is Err } + return count(Result::isErr) } /** @@ -93,9 +95,11 @@ public inline fun Iterable.fold( var accumulator = initial for (element in this) { - accumulator = when (val result = operation(accumulator, element)) { - is Ok -> result.value - is Err -> return Err(result.error) + val result = operation(accumulator, element) + + accumulator = when { + result.isOk -> result.value + else -> return Err(result.error) } } @@ -106,7 +110,7 @@ public inline fun Iterable.fold( * Accumulates value starting with [initial] value and applying [operation] from right to left to * each element and current accumulator value. */ -public inline fun > List.foldRight( +public inline fun List.foldRight( initial: R, operation: (T, acc: R) -> Result, ): Result { @@ -116,9 +120,11 @@ public inline fun > List.foldRight( val iterator = listIterator(size) while (iterator.hasPrevious()) { - accumulator = when (val result = operation(iterator.previous(), accumulator)) { - is Ok -> result.value - is Err -> return Err(result.error) + val result = operation(iterator.previous(), accumulator) + + accumulator = when { + result.isOk -> result.value + else -> return Err(result.error) } } } @@ -127,8 +133,8 @@ public inline fun > List.foldRight( } /** - * Combines a vararg of [Results][Result] into a single [Result] (holding a [List]). Elements in the returned list - * are in the same order is the input vararg. + * Combines the specified [results] into a single [Result] (holding a [List]). Elements in the + * returned list are in the same order as the specified [results]. * * - Elm: [Result.Extra.combine](http://package.elm-lang.org/packages/elm-community/result-extra/2.2.0/Result-Extra#combine) */ @@ -137,16 +143,16 @@ public fun > combine(vararg results: R): Result, } /** - * Combines an [Iterable] of [Results][Result] into a single [Result] (holding a [List]). Elements in the returned - * list are in the input [Iterable] order. + * Combines [this] iterable into a single [Result] (holding a [List]). Elements in the returned + * list are in the the same order as [this]. * * - Elm: [Result.Extra.combine](http://package.elm-lang.org/packages/elm-community/result-extra/2.2.0/Result-Extra#combine) */ public fun Iterable>.combine(): Result, E> { - val values = map { - when (it) { - is Ok -> it.value - is Err -> return it + val values = map { result -> + when { + result.isOk -> result.value + else -> return result.asErr() } } @@ -154,8 +160,9 @@ public fun Iterable>.combine(): Result, E> { } /** - * Extracts from a vararg of [Results][Result] all the [Ok] elements. All the [Ok] elements are - * extracted in order. + * Returns a [List] containing the [value][Result.value] of each element in the specified [results] + * that [is ok][Result.isOk]. Elements in the returned list are in the same order as the specified + * [results]. * * - Haskell: [Data.Either.lefts](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Either.html#v:lefts) */ @@ -178,8 +185,9 @@ public fun Iterable>.getAll(): List { } /** - * Extracts from a vararg of [Results][Result] all the [Err] elements. All the [Err] elements are - * extracted in order. + * Returns a [List] containing the [error][Result.error] of each element in the specified [results] + * that [is an error][Result.isErr]. Elements in the returned list are in the same order as the + * specified [results]. * * - Haskell: [Data.Either.rights](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Either.html#v:rights) */ @@ -202,9 +210,9 @@ public fun Iterable>.getAllErrors(): List { } /** - * Partitions a vararg of [Results][Result] into a [Pair] of [Lists][List]. All the [Ok] elements - * are extracted, in order, to the [first][Pair.first] value. Similarly the [Err] elements are - * extracted to the [Pair.second] value. + * Partitions the specified [results] into a [Pair] of [Lists][List]. An element that + * [is ok][Result.isOk] will appear in the [first][Pair.first] list, whereas an element that + * [is an error][Result.isErr] will appear in the [second][Pair.second] list. * * - Haskell: [Data.Either.partitionEithers](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Either.html#v:partitionEithers) */ @@ -213,9 +221,10 @@ public fun > partition(vararg results: R): Pair, } /** - * Partitions an [Iterable] of [Results][Result] into a [Pair] of [Lists][List]. All the [Ok] - * elements are extracted, in order, to the [first][Pair.first] value. Similarly the [Err] elements - * are extracted to the [Pair.second] value. + * + * Partitions this into a [Pair] of [Lists][List]. An element that [is ok][Result.isOk] will appear + * in the [first][Pair.first] list, whereas an element that [is an error][Result.isErr] will appear + * in the [second][Pair.second] list. * * - Haskell: [Data.Either.partitionEithers](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Either.html#v:partitionEithers) */ @@ -223,10 +232,11 @@ public fun Iterable>.partition(): Pair, List> { val values = mutableListOf() val errors = mutableListOf() - forEach { result -> - when (result) { - is Ok -> values.add(result.value) - is Err -> errors.add(result.error) + for (result in this) { + if (result.isOk) { + values += result.value + } else { + errors += result.error } } @@ -236,15 +246,17 @@ public fun Iterable>.partition(): Pair, List> { /** * Returns a [Result, E>][Result] containing the results of applying the given [transform] * function to each element in the original collection, returning early with the first [Err] if a - * transformation fails. Elements in the returned list are in the input [Iterable] order. + * transformation fails. Elements in the returned list are in the same order as [this]. */ public inline fun Iterable.mapResult( transform: (V) -> Result, ): Result, E> { val values = map { element -> - when (val transformed = transform(element)) { - is Ok -> transformed.value - is Err -> return transformed + val transformed = transform(element) + + when { + transformed.isOk -> transformed.value + else -> return transformed.asErr() } } @@ -254,16 +266,18 @@ public inline fun Iterable.mapResult( /** * Applies the given [transform] function to each element of the original collection and appends * the results to the given [destination], returning early with the first [Err] if a - * transformation fails. Elements in the returned list are in the input [Iterable] order. + * transformation fails. Elements in the returned list are in the same order as [this]. */ public inline fun > Iterable.mapResultTo( destination: C, transform: (V) -> Result, ): Result { val values = mapTo(destination) { element -> - when (val transformed = transform(element)) { - is Ok -> transformed.value - is Err -> return transformed + val transformed = transform(element) + + when { + transformed.isOk -> transformed.value + else -> return transformed.asErr() } } @@ -273,17 +287,19 @@ public inline fun > Iterable.mapResultTo /** * Returns a [Result, E>][Result] containing only the non-null results of applying the * given [transform] function to each element in the original collection, returning early with the - * first [Err] if a transformation fails. Elements in the returned list are in the input [Iterable] - * order. + * first [Err] if a transformation fails. Elements in the returned list are in the same order as + * [this]. */ public inline fun Iterable.mapResultNotNull( transform: (V) -> Result?, ): Result, E> { val values = mapNotNull { element -> - when (val transformed = transform(element)) { - is Ok -> transformed.value - is Err -> return transformed - null -> null + val transformed = transform(element) + + when { + transformed == null -> null + transformed.isOk -> transformed.value + else -> return transformed.asErr() } } @@ -300,10 +316,12 @@ public inline fun > Iterable.mapRe transform: (V) -> Result?, ): Result { val values = mapNotNullTo(destination) { element -> - when (val transformed = transform(element)) { - is Ok -> transformed.value - is Err -> return transformed - null -> null + val transformed = transform(element) + + when { + transformed == null -> null + transformed.isOk -> transformed.value + else -> return transformed.asErr() } } @@ -313,16 +331,18 @@ public inline fun > Iterable.mapRe /** * Returns a [Result, E>][Result] containing the results of applying the given [transform] * function to each element and its index in the original collection, returning early with the - * first [Err] if a transformation fails. Elements in the returned list are in the input [Iterable] - * order. + * first [Err] if a transformation fails. Elements in the returned list are in same order as + * [this]. */ public inline fun Iterable.mapResultIndexed( transform: (index: Int, V) -> Result, ): Result, E> { val values = mapIndexed { index, element -> - when (val transformed = transform(index, element)) { - is Ok -> transformed.value - is Err -> return transformed + val transformed = transform(index, element) + + when { + transformed.isOk -> transformed.value + else -> return transformed.asErr() } } @@ -339,9 +359,11 @@ public inline fun > Iterable.mapResultIn transform: (index: Int, V) -> Result, ): Result { val values = mapIndexedTo(destination) { index, element -> - when (val transformed = transform(index, element)) { - is Ok -> transformed.value - is Err -> return transformed + val transformed = transform(index, element) + + when { + transformed.isOk -> transformed.value + else -> return transformed.asErr() } } @@ -352,16 +374,18 @@ public inline fun > Iterable.mapResultIn * Returns a [Result, E>][Result] containing only the non-null results of applying the * given [transform] function to each element and its index in the original collection, returning * early with the first [Err] if a transformation fails. Elements in the returned list are in - * the input [Iterable] order. + * the same order as [this]. */ public inline fun Iterable.mapResultIndexedNotNull( transform: (index: Int, V) -> Result?, ): Result, E> { val values = mapIndexedNotNull { index, element -> - when (val transformed = transform(index, element)) { - is Ok -> transformed.value - is Err -> return transformed - null -> null + val transformed = transform(index, element) + + when { + transformed == null -> null + transformed.isOk -> transformed.value + else -> return transformed.asErr() } } @@ -378,10 +402,12 @@ public inline fun > Iterable.mapRe transform: (index: Int, V) -> Result?, ): Result { val values = mapIndexedNotNullTo(destination) { index, element -> - when (val transformed = transform(index, element)) { - is Ok -> transformed.value - is Err -> return transformed - null -> null + val transformed = transform(index, element) + + when { + transformed == null -> null + transformed.isOk -> transformed.value + else -> return transformed.asErr() } } diff --git a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Map.kt b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Map.kt index 5449bf2..4a69fe1 100644 --- a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Map.kt +++ b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Map.kt @@ -5,7 +5,7 @@ import kotlin.contracts.contract /** * Maps this [Result][Result] to [Result][Result] by either applying the [transform] - * function to the [value][Ok.value] if this [Result] is [Ok], or returning this [Err]. + * function to the [value][Result.value] if this result [is ok][Result.isOk], or returning [this]. * * - Elm: [Result.map](http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map) * - Haskell: [Data.Bifunctor.first](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Bifunctor.html#v:first) @@ -16,19 +16,19 @@ public inline infix fun Result.map(transform: (V) -> U): Result< callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Ok -> Ok(transform(value)) - is Err -> this + return when { + isOk -> Ok(transform(value)) + else -> this.asErr() } } /** * Maps this [Result][Result] to [Result][Result] by either applying - * the [transform] function to the [value][Ok.value] if this [Result] is [Ok], or returning this - * [Err]. + * the [transform] function to the [value][Result.value] if this result [is ok][Result.isOk], or + * returning [this]. * * This function catches any [Throwable] exception thrown by [transform] function and encapsulates - * it as a failure. + * it as an [Err]. * * - Elm: [Result.map](http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map) * - Haskell: [Data.Bifunctor.first](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Bifunctor.html#v:first) @@ -39,9 +39,9 @@ public inline infix fun Result.mapCatching(transform: (V) - callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Ok -> runCatching { transform(value) } - is Err -> this + return when { + isOk -> runCatching { transform(value) } + else -> this.asErr() } } @@ -53,14 +53,10 @@ public inline infix fun Result.mapCatching(transform: (V) - * - Rust: [Result.transpose][https://doc.rust-lang.org/std/result/enum.Result.html#method.transpose] */ public inline fun Result.transpose(): Result? { - return when (this) { - is Ok -> if (value == null) { - null - } else { - Ok(value) - } - - is Err -> this + return when { + isOk && value == null -> null + isOk && value != null -> this.asOk() + else -> this.asErr() } } @@ -69,16 +65,16 @@ public inline fun Result.transpose(): Result? { * * - Rust: [Result.flatten](https://doc.rust-lang.org/std/result/enum.Result.html#method.flatten) */ -public inline fun Result, E>.flatten(): Result { - return when (this) { - is Ok -> value - is Err -> this +public fun Result, E>.flatten(): Result { + return when { + isOk -> value + else -> this.asErr() } } /** * Maps this [Result][Result] to [Result][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]. * * This is functionally equivalent to [andThen]. * @@ -94,7 +90,8 @@ public inline infix fun Result.flatMap(transform: (V) -> Result< /** * Maps this [Result][Result] to [U] by applying either the [success] function if this - * [Result] is [Ok], or the [failure] function if this [Result] is an [Err]. + * result [is ok][Result.isOk], or the [failure] function if this result + * [is an error][Result.isErr]. * * Unlike [mapEither], [success] and [failure] must both return [U]. * @@ -110,15 +107,16 @@ public inline fun Result.mapBoth( callsInPlace(failure, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Ok -> success(value) - is Err -> failure(error) + return when { + isOk -> success(value) + else -> failure(error) } } /** * Maps this [Result][Result] to [U] by applying either the [success] function if this - * [Result] is [Ok], or the [failure] function if this [Result] is an [Err]. + * result [is ok][Result.isOk], or the [failure] function if this result + * [is an error][Result.isErr]. * * Unlike [mapEither], [success] and [failure] must both return [U]. * @@ -141,7 +139,8 @@ public inline fun Result.fold( /** * Maps this [Result][Result] to [Result][Result] by applying either the [success] - * function if this [Result] is [Ok], or the [failure] function if this [Result] is an [Err]. + * function if this result [is ok][Result.isOk], or the [failure] function if this result + * [is an error][Result.isErr]. * * Unlike [mapEither], [success] and [failure] must both return [U]. * @@ -157,15 +156,16 @@ public inline fun Result.flatMapBoth( callsInPlace(failure, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Ok -> success(value) - is Err -> failure(error) + return when { + isOk -> success(value) + else -> failure(error) } } /** * Maps this [Result][Result] to [Result][Result] by applying either the [success] - * function if this [Result] is [Ok], or the [failure] function if this [Result] is an [Err]. + * function if this result [is ok][Result.isOk], or the [failure] function if this result + * [is an error][Result.isErr]. * * Unlike [mapBoth], [success] and [failure] may either return [U] or [F] respectively. * @@ -180,15 +180,16 @@ public inline fun Result.mapEither( callsInPlace(failure, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Ok -> Ok(success(value)) - is Err -> Err(failure(error)) + return when { + isOk -> Ok(success(value)) + else -> Err(failure(error)) } } /** * Maps this [Result][Result] to [Result][Result] by applying either the [success] - * function if this [Result] is [Ok], or the [failure] function if this [Result] is an [Err]. + * function if this result [is ok][Result.isOk], or the [failure] function if this result + * [is an error][Result.isErr]. * * Unlike [mapBoth], [success] and [failure] may either return [U] or [F] respectively. * @@ -203,15 +204,16 @@ public inline fun Result.flatMapEither( callsInPlace(failure, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Ok -> success(value) - is Err -> failure(error) + return when { + isOk -> success(value) + else -> failure(error) } } /** * Maps this [Result][Result] to [Result][Result] by either applying the [transform] - * function to the [error][Err.error] if this [Result] is [Err], or returning this [Ok]. + * function to the [error][Result.error] if this result [is an error][Result.isErr], or returning + * [this]. * * - Elm: [Result.mapError](http://package.elm-lang.org/packages/elm-lang/core/latest/Result#mapError) * - Haskell: [Data.Bifunctor.right](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Bifunctor.html#v:second) @@ -222,46 +224,52 @@ public inline infix fun Result.mapError(transform: (E) -> F): Re callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Ok -> this - is Err -> Err(transform(error)) + return when { + isErr -> Err(transform(error)) + else -> this.asOk() } } /** * Maps this [Result][Result] to [U] by either applying the [transform] function to the - * [value][Ok.value] if this [Result] is [Ok], or returning the [default] if this [Result] is an - * [Err]. + * [value][Result.value] if this result [is ok][Result.isOk], or returning the [default] if this + * result [is an error][Result.isErr]. * * - Rust: [Result.map_or](https://doc.rust-lang.org/std/result/enum.Result.html#method.map_or) */ -public inline fun Result.mapOr(default: U, transform: (V) -> U): U { +public inline fun Result.mapOr( + default: U, + transform: (V) -> U, +): U { contract { callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Ok -> transform(value) - is Err -> default + return when { + isOk -> transform(value) + else -> default } } /** * Maps this [Result][Result] to [U] by applying either the [transform] function if this - * [Result] is [Ok], or the [default] function if this [Result] is an [Err]. Both of these - * functions must return the same type ([U]). + * result [is ok][Result.isOk], or the [default] function if this result + * [is an error][Result.isErr]. Both of these functions must return the same type ([U]). * * - Rust: [Result.map_or_else](https://doc.rust-lang.org/std/result/enum.Result.html#method.map_or_else) */ -public inline fun Result.mapOrElse(default: (E) -> U, transform: (V) -> U): U { +public inline fun Result.mapOrElse( + default: (E) -> U, + transform: (V) -> U, +): U { contract { callsInPlace(default, InvocationKind.AT_MOST_ONCE) callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Ok -> transform(value) - is Err -> default(error) + return when { + isOk -> transform(value) + else -> default(error) } } @@ -273,35 +281,40 @@ public inline fun Result.mapOrElse(default: (E) -> U, transform: public inline infix fun Result, E>.mapAll(transform: (V) -> Result): Result, E> { return map { iterable -> iterable.map { element -> - when (val transformed = transform(element)) { - is Ok -> transformed.value - is Err -> return transformed + val transformed = transform(element) + + when { + transformed.isOk -> transformed.value + else -> return transformed.asErr() } } } } /** - * Returns the [transformation][transform] of the [value][Ok.value] if this [Result] is [Ok] - * and satisfies the given [predicate], otherwise this [Result]. + * Returns the [transformation][transform] of the [value][Result.value] if this result + * [is ok][Result.isOk] and satisfies the given [predicate], otherwise [this]. * * @see [takeIf] */ -public inline fun Result.toErrorIf(predicate: (V) -> Boolean, transform: (V) -> E): Result { +public inline fun Result.toErrorIf( + predicate: (V) -> Boolean, + transform: (V) -> E, +): Result { contract { callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } return when { - this is Ok && predicate(value) -> Err(transform(value)) + isOk && predicate(value) -> Err(transform(value)) else -> this } } /** - * Returns the supplied [error] if this [Result] is [Ok] and the [value][Ok.value] is `null`, - * otherwise this [Result]. + * Returns the supplied [error] if this result [is ok][Result.isOk] and the [value][Result.value] + * is `null`, otherwise [this]. * * @see [toErrorIf] */ @@ -310,43 +323,37 @@ public inline fun Result.toErrorIfNull(error: () -> E): Result if (value == null) { - Err(error()) - } else { - Ok(value) - } - - is Err -> this + return when { + isOk && value == null -> Err(error()) + isOk && value != null -> this.asOk() + else -> this.asErr() } } /** - * Returns the [transformation][transform] of the [value][Ok.value] if this [Result] is [Ok] - * and _does not_ satisfy the given [predicate], otherwise this [Result]. + * Returns the [transformation][transform] of the [value][Result.value] if this result + * [is ok][Result.isOk] and _does not_ satisfy the given [predicate], otherwise [this]. * * @see [takeUnless] */ -public inline fun Result.toErrorUnless(predicate: (V) -> Boolean, transform: (V) -> E): Result { +public inline fun Result.toErrorUnless( + predicate: (V) -> Boolean, + transform: (V) -> E, +): Result { contract { callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Ok -> if (!predicate(value)) { - Err(transform(value)) - } else { - this - } - - is Err -> this + return when { + isOk && !predicate(value) -> Err(transform(value)) + else -> this } } /** - * Returns the supplied [error] unless this [Result] is [Ok] and the [value][Ok.value] is `null`, - * otherwise this [Result]. + * Returns the supplied [error] unless this result [is ok][Result.isOk] and the + * [value][Result.value] is `null`, otherwise [this]. * * @see [toErrorUnless] */ @@ -355,13 +362,8 @@ public inline fun Result.toErrorUnlessNull(error: () -> E): Result< callsInPlace(error, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Ok -> if (value == null) { - this - } else { - Err(error()) - } - - is Err -> Err(error()) + return when { + isOk && value == null -> this + else -> Err(error()) } } diff --git a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/On.kt b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/On.kt index ba6acb3..3cfa15d 100644 --- a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/On.kt +++ b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/On.kt @@ -4,7 +4,7 @@ import kotlin.contracts.InvocationKind import kotlin.contracts.contract /** - * Invokes an [action] if this [Result] is [Ok]. + * Invokes an [action] if this result [is ok][Result.isOk]. * * - Rust: [Result.inspect](https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect) */ @@ -13,7 +13,7 @@ public inline infix fun Result.onSuccess(action: (V) -> Unit): Resu callsInPlace(action, InvocationKind.AT_MOST_ONCE) } - if (this is Ok) { + if (isOk) { action(value) } @@ -21,7 +21,7 @@ public inline infix fun Result.onSuccess(action: (V) -> Unit): Resu } /** - * Invokes an [action] if this [Result] is [Err]. + * Invokes an [action] if this result [is an error][Result.isErr]. * * - Rust [Result.inspect_err](https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect_err) */ @@ -30,7 +30,7 @@ public inline infix fun Result.onFailure(action: (E) -> Unit): Resu callsInPlace(action, InvocationKind.AT_MOST_ONCE) } - if (this is Err) { + if (isErr) { action(error) } diff --git a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Or.kt b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Or.kt index fc4063a..c0f20b9 100644 --- a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Or.kt +++ b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Or.kt @@ -4,14 +4,14 @@ import kotlin.contracts.InvocationKind import kotlin.contracts.contract /** - * Returns [result] if this [Result] is [Err], otherwise this [Ok]. + * Returns [result] if this result [is an error][Result.isErr], otherwise [this]. * * - Rust: [Result.or](https://doc.rust-lang.org/std/result/enum.Result.html#method.or) */ public infix fun Result.or(result: Result): Result { - return when (this) { - is Ok -> this - is Err -> result + return when { + isOk -> this.asOk() + else -> result } } @@ -25,8 +25,8 @@ public inline infix fun Result.or(result: () -> Result): R } /** - * Returns the [transformation][transform] of the [error][Err.error] if this [Result] is [Err], - * otherwise this [Ok]. + * Returns the [transformation][transform] of the [error][Result.error] if this result + * [is an error][Result.isErr], otherwise [this]. * * - Rust: [Result.or_else](https://doc.rust-lang.org/std/result/enum.Result.html#method.or_else) */ @@ -35,25 +35,26 @@ public inline infix fun Result.orElse(transform: (E) -> Result this - is Err -> transform(error) + return when { + isOk -> this.asOk() + else -> transform(error) } } /** - * Throws the [error][Err.error] if this [Result] is [Err], otherwise returns this [Ok]. + * Throws the [error][Result.error] if this result [is an error][Result.isErr], otherwise returns + * [this]. */ -public fun Result.orElseThrow(): Ok { - return when (this) { - is Ok -> this - is Err -> throw error +public fun Result.orElseThrow(): Result { + return when { + isOk -> this.asOk() + else -> throw error } } /** - * Throws the [error][Err.error] if this [Result] is an [Err] and satisfies the given - * [predicate], otherwise returns this [Result]. + * Throws the [error][Result.error] if this result [is an error][Result.isErr] and satisfies the + * given [predicate], otherwise returns [this]. * * @see [takeIf] */ @@ -63,14 +64,14 @@ public inline fun Result.throwIf(predicate: (E) -> Bool } return when { - this is Err && predicate(error) -> throw error + isErr && predicate(error) -> throw error else -> this } } /** - * Throws the [error][Err.error] if this [Result] is an [Err] and _does not_ satisfy the - * given [predicate], otherwise returns this [Result]. + * Throws the [error][Result.error] if this result [is an error][Result.isErr] and _does not_ + * satisfy the given [predicate], otherwise returns [this]. * * @see [takeUnless] */ @@ -79,12 +80,8 @@ public inline fun Result.throwUnless(predicate: (E) -> callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Ok -> this - is Err -> if (!predicate(error)) { - throw error - } else { - this - } + return when { + isErr && !predicate(error) -> throw error + else -> this } } diff --git a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Recover.kt b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Recover.kt index a949617..97ba1b0 100644 --- a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Recover.kt +++ b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Recover.kt @@ -4,54 +4,58 @@ import kotlin.contracts.InvocationKind import kotlin.contracts.contract /** - * Returns the [transformation][transform] of the [error][Err.error] if this [Result] is [Err], - * otherwise this [Ok]. + * Returns the [transformation][transform] of the [error][Result.error] if this result + * [is an error][Result.isErr], otherwise [this]. */ -public inline infix fun Result.recover(transform: (E) -> V): Ok { +public inline infix fun Result.recover(transform: (E) -> V): Result { contract { callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Ok -> this - is Err -> Ok(transform(error)) + return when { + isOk -> this.asOk() + else -> Ok(transform(error)) } } /** - * Returns the [transformation][transform] of the [error][Err.error], catching and encapsulating any - * thrown exception as a failure if this [Result] is [Err], otherwise this [Ok]. + * Returns the [transformation][transform] of the [error][Result.error] if this result + * [is an error][Result.isErr], catching and encapsulating any thrown exception as an [Err], + * otherwise [this]. */ public inline infix fun Result.recoverCatching(transform: (E) -> V): Result { contract { callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Ok -> this - is Err -> runCatching { transform(error) } + return when { + isOk -> this.asOk() + else -> runCatching { transform(error) } } } /** - * Returns the [transformation][transform] of the [error][Err.error] if this [Result] is [Err] - * and satisfies the given [predicate], otherwise this [Result]. + * Returns the [transformation][transform] of the [error][Result.error] if this result + * [is an error][Result.isErr] and satisfies the given [predicate], otherwise [this]. */ -public inline fun Result.recoverIf(predicate: (E) -> Boolean, transform: (E) -> V): Result { +public inline fun Result.recoverIf( + predicate: (E) -> Boolean, + transform: (E) -> V, +): Result { contract { callsInPlace(predicate, InvocationKind.AT_MOST_ONCE) callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } return when { - this is Err && predicate(error) -> Ok(transform(error)) + isErr && predicate(error) -> Ok(transform(error)) else -> this } } /** - * Returns the [transformation][transform] of the [error][Err.error] if this [Result] is [Err] - * and _does not_ satisfy the given [predicate], otherwise this [Result]. + * Returns the [transformation][transform] of the [error][Result.error] if this result + * [is an error][Result.isErr] and _does not_ satisfy the given [predicate], otherwise [this]. */ public inline fun Result.recoverUnless(predicate: (E) -> Boolean, transform: (E) -> V): Result { contract { @@ -60,29 +64,29 @@ public inline fun Result.recoverUnless(predicate: (E) -> Boolean, t } return when { - this is Err && !predicate(error) -> Ok(transform(error)) + isErr && !predicate(error) -> Ok(transform(error)) else -> this } } /** - * Returns the [transformation][transform] of the [error][Err.error] if this [Result] is [Err], - * otherwise this [Result]. + * Returns the [transformation][transform] of the [error][Result.error] if this result + * [is an error][Result.isErr], otherwise [this]. */ public inline fun Result.andThenRecover(transform: (E) -> Result): Result { contract { callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } - return when (this) { - is Ok -> this - is Err -> transform(error) + return when { + isOk -> this + else -> transform(error) } } /** - * Returns the [transformation][transform] of the [error][Err.error] if this [Result] is [Err] and - * satisfies the given [predicate], otherwise this [Result]. + * Returns the [transformation][transform] of the [error][Result.error] if this result + * [is an error][Result.isErr] and satisfies the given [predicate], otherwise [this]. */ public inline fun Result.andThenRecoverIf( predicate: (E) -> Boolean, @@ -94,14 +98,14 @@ public inline fun Result.andThenRecoverIf( } return when { - this is Err && predicate(error) -> transform(error) + isErr && predicate(error) -> transform(error) else -> this } } /** - * Returns the [transformation][transform] of the [error][Err.error] if this [Result] is [Err] - * and _does not_ satisfy the given [predicate], otherwise this [Result]. + * Returns the [transformation][transform] of the [error][Result.error] if this result + * [is an error][Result.isErr] and _does not_ satisfy the given [predicate], otherwise [this]. */ public inline fun Result.andThenRecoverUnless( predicate: (E) -> Boolean, @@ -113,7 +117,7 @@ public inline fun Result.andThenRecoverUnless( } return when { - this is Err && !predicate(error) -> transform(error) + isErr && !predicate(error) -> transform(error) else -> this } } diff --git a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Result.kt b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Result.kt index 3541325..3eb5c53 100644 --- a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Result.kt +++ b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Result.kt @@ -1,5 +1,39 @@ package com.github.michaelbull.result +/** + * Returns a [Result] that [is ok][Result.isOk] and contains a [value][Result.value]. + */ +@Suppress("FunctionName", "DEPRECATION") +public fun Ok(value: V): Result { + return Ok(value, null) +} + +/** + * Returns a [Result] that [is an error][Result.isErr] and contains an [error][Result.error]. + */ +@Suppress("FunctionName", "DEPRECATION") +public fun Err(error: E): Result { + return Err(error, null) +} + +/** + * Unsafely casts this [Result][Result] to [Result][Result], to be used inside + * an explicit [isOk][Result.isOk] or [isErr][Result.isErr] guard. + */ +@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE") +public inline fun Result.asOk(): Result { + return this as Result +} + +/** + * Unsafely casts this [Result][Result] to [Result][Result], to be used inside + * an explicit [isOk][Result.isOk] or [isErr][Result.isErr] guard. + */ +@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE") +public inline fun Result.asErr(): Result { + return this as Result +} + /** * [Result] is a type that represents either success ([Ok]) or failure ([Err]). * @@ -9,6 +43,12 @@ package com.github.michaelbull.result */ public sealed class Result { + public abstract val value: V + public abstract val error: E + + public abstract val isOk: Boolean + public abstract val isErr: Boolean + public abstract operator fun component1(): V? public abstract operator fun component2(): E? @@ -32,7 +72,22 @@ public sealed class Result { /** * Represents a successful [Result], containing a [value]. */ -public class Ok(public val value: V) : Result() { +@Deprecated( + message = "Using Ok as a return type is deprecated.", + replaceWith = ReplaceWith("Result"), +) +public class Ok internal constructor( + override val value: V, + @Suppress("UNUSED_PARAMETER") placeholder: Any?, +) : Result() { + + override val error: Nothing + get() { + throw NoSuchElementException() + } + + override val isOk: Boolean = true + override val isErr: Boolean = false override fun component1(): V = value override fun component2(): Nothing? = null @@ -55,7 +110,22 @@ public class Ok(public val value: V) : Result() { /** * Represents a failed [Result], containing an [error]. */ -public class Err(public val error: E) : Result() { +@Deprecated( + message = "Using Err as a return type is deprecated.", + replaceWith = ReplaceWith("Result"), +) +public class Err internal constructor( + override val error: E, + @Suppress("UNUSED_PARAMETER") placeholder: Any?, +) : Result() { + + override val value: Nothing + get() { + throw NoSuchElementException() + } + + override val isOk: Boolean = false + override val isErr: Boolean = true override fun component1(): Nothing? = null override fun component2(): E = error diff --git a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/ResultIterator.kt b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/ResultIterator.kt index 8c7e0c3..1c3c065 100644 --- a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/ResultIterator.kt +++ b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/ResultIterator.kt @@ -1,8 +1,10 @@ package com.github.michaelbull.result /** - * Returns an [Iterator] over the possibly contained [value][Ok.value]. - * The iterator yields one [value][Ok.value] if the [Result] is [Ok], otherwise throws [NoSuchElementException]. + * Returns an [Iterator] over the possibly contained [value][Result.value]. + * + * The iterator yields one [value][Result.value] if this result [is ok][Result.isOk], otherwise + * throws [NoSuchElementException]. * * - Rust: [Result.iter](https://doc.rust-lang.org/std/result/enum.Result.html#method.iter) */ @@ -11,8 +13,10 @@ public fun Result.iterator(): Iterator { } /** - * Returns a [MutableIterator] over the possibly contained [value][Ok.value]. - * The iterator yields one [value][Ok.value] if the [Result] is [Ok], otherwise throws [NoSuchElementException]. + * Returns a [MutableIterator] over the possibly contained [value][Result.value]. + * + * The iterator yields one [value][Result.value] if this result [is ok][Result.isOk], otherwise + * throws [NoSuchElementException]. * * - Rust: [Result.iter_mut](https://doc.rust-lang.org/std/result/enum.Result.html#method.iter_mut) */ @@ -23,31 +27,29 @@ public fun Result.mutableIterator(): MutableIterator { private class ResultIterator(private val result: Result) : MutableIterator { /** - * A flag indicating whether this [Iterator] has [yielded] its [Result]. + * A flag indicating whether this [Iterator] has [yielded] the [value][Result.value] of the + * [result]. */ private var yielded = false /** - * @return `true` if the [value][Ok.value] is not [yielded] and [Ok], `false` otherwise. + * @return `true` if this [Iterator] has [yielded] the [value][Result.value] of the [result], + * `false` otherwise. */ override fun hasNext(): Boolean { - if (yielded) { - return false - } - - return when (result) { - is Ok -> true - is Err -> false - } + return !yielded && result.isOk } /** - * Returns the [Result's][Result] [value][Ok.value] if not [yielded] and [Ok]. - * @throws NoSuchElementException if the [Result] is [yielded] or is not [Ok]. + * Returns the [value][Result.value] of the [result] if not [yielded] and the result + * [is ok][Result.isOk]. + * + * @throws NoSuchElementException if already [yielded] or the result + * [is an error][Result.isErr]. */ override fun next(): V { - if (!yielded && result is Ok) { - yielded = true + if (hasNext()) { + remove() return result.value } else { throw NoSuchElementException() @@ -55,7 +57,7 @@ private class ResultIterator(private val result: Result) : M } /** - * Flags this [Iterator] as having [yielded] its [Result]. + * Flags this [Iterator] as having [yielded] the [value][Result.value] of the [result]. */ override fun remove() { yielded = true diff --git a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Unwrap.kt b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Unwrap.kt index 283fbbb..9c5d42e 100644 --- a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Unwrap.kt +++ b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Unwrap.kt @@ -6,20 +6,21 @@ import kotlin.contracts.contract public class UnwrapException(message: String) : Exception(message) /** - * Unwraps a [Result], yielding the [value][Ok.value]. + * Returns the [value][Result.value] if this result [is ok][Result.isOk], otherwise throws an + * [UnwrapException]. * * - Rust: [Result.unwrap](https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap) * - * @throws UnwrapException if the [Result] is an [Err], with a message containing the [error][Err.error]. + * @throws UnwrapException if this result [is an error][Result.isErr]. */ public fun Result.unwrap(): V { contract { returns() implies (this@unwrap is Ok) } - return when (this) { - is Ok -> value - is Err -> throw UnwrapException("called Result.unwrap on an Err value $error") + return when { + isOk -> value + else -> throw UnwrapException("called Result.unwrap on an Err value $error") } } @@ -33,12 +34,15 @@ public infix fun Result.expect(message: String): V { } /** - * Unwraps a [Result], yielding the [value][Ok.value]. + * Returns the [value][Result.value] if this result [is ok][Result.isOk], otherwise throws an + * [UnwrapException] with the specified [message]. * * - Rust: [Result.expect](https://doc.rust-lang.org/std/result/enum.Result.html#method.expect) * - * @param message The message to include in the [UnwrapException] if the [Result] is an [Err]. - * @throws UnwrapException if the [Result] is an [Err], with the specified [message]. + * @param message The message to include in the [UnwrapException] if this result + * [is an error][Result.isErr]. + * + * @throws UnwrapException if this result [is an error][Result.isErr]. */ public inline infix fun Result.expect(message: () -> Any): V { contract { @@ -46,27 +50,28 @@ public inline infix fun Result.expect(message: () -> Any): V { returns() implies (this@expect is Ok) } - return when (this) { - is Ok -> value - is Err -> throw UnwrapException("${message()} $error") + return when { + isOk -> value + else -> throw UnwrapException("${message()} $error") } } /** - * Unwraps a [Result], yielding the [error][Err.error]. + * Returns the [error][Result.error] if this result [is an error][Result.isErr], otherwise throws + * an [UnwrapException]. * * - Rust: [Result.unwrap_err](https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_err) * - * @throws UnwrapException if the [Result] is [Ok], with a message containing the [value][Ok.value]. + * @throws UnwrapException if this result [is ok][Result.isOk]. */ public fun Result.unwrapError(): E { contract { returns() implies (this@unwrapError is Err) } - return when (this) { - is Ok -> throw UnwrapException("called Result.unwrapError on an Ok value $value") - is Err -> error + return when { + isErr -> error + else -> throw UnwrapException("called Result.unwrapError on an Ok value $value") } } @@ -80,12 +85,15 @@ public infix fun Result.expectError(message: String): E { } /** - * Unwraps a [Result], yielding the [error][Err.error]. + * Returns the [error][Result.error] if this result [is an error][Result.isErr], otherwise throws + * an [UnwrapException] with the specified [message]. * * - Rust: [Result.expect_err](https://doc.rust-lang.org/std/result/enum.Result.html#method.expect_err) * - * @param message The message to include in the [UnwrapException] if the [Result] is [Ok]. - * @throws UnwrapException if the [Result] is [Ok], with the specified [message]. + * @param message The message to include in the [UnwrapException] if this result + * [is ok][Result.isOk]. + * + * @throws UnwrapException if this result [is ok][Result.isOk]. */ public inline infix fun Result.expectError(message: () -> Any): E { contract { @@ -93,8 +101,8 @@ public inline infix fun Result.expectError(message: () -> Any): E { returns() implies (this@expectError is Err) } - return when (this) { - is Ok -> throw UnwrapException("${message()} $value") - is Err -> error + return when { + isErr -> error + else -> throw UnwrapException("${message()} $value") } } diff --git a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Zip.kt b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Zip.kt index 82b0b98..9dbb864 100644 --- a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Zip.kt +++ b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Zip.kt @@ -6,8 +6,8 @@ import kotlin.contracts.contract private typealias Producer = () -> Result /** - * Apply a [transformation][transform] to two [Results][Result], if both [Results][Result] are [Ok]. - * If not, the first argument which is an [Err] will propagate through. + * Applies the given [transform] function to two [Results][Result], returning early with the first + * [Err] if a transformation fails. * * - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map2 */ @@ -30,8 +30,8 @@ public inline fun zip( } /** - * Apply a [transformation][transform] to three [Results][Result], if all [Results][Result] are [Ok]. - * If not, the first argument which is an [Err] will propagate through. + * Applies the given [transform] function to three [Results][Result], returning early with the + * first [Err] if a transformation fails. * * - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map3 */ @@ -58,8 +58,8 @@ public inline fun zip( } /** - * Apply a [transformation][transform] to four [Results][Result], if all [Results][Result] are [Ok]. - * If not, the first argument which is an [Err] will propagate through. + * Applies the given [transform] function to four [Results][Result], returning early with the + * first [Err] if a transformation fails. * * - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map4 */ @@ -90,8 +90,8 @@ public inline fun zip( } /** - * Apply a [transformation][transform] to five [Results][Result], if all [Results][Result] are [Ok]. - * If not, the first argument which is an [Err] will propagate through. + * Applies the given [transform] function to five [Results][Result], returning early with the + * first [Err] if a transformation fails. * * - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map5 */ @@ -126,8 +126,8 @@ public inline fun zip( } /** - * Apply a [transformation][transform] to two [Results][Result], if both [Results][Result] are [Ok]. - * If not, the all arguments which are [Err] will be collected. + * Applies the given [transform] function to two [Results][Result], collecting any result that + * [is an error][Result.isErr] to a [List]. */ public inline fun zipOrAccumulate( producer1: () -> Result, @@ -143,10 +143,12 @@ public inline fun zipOrAccumulate( val result1 = producer1() val result2 = producer2() - return if ( - result1 is Ok && - result2 is Ok - ) { + val results = listOf( + producer1(), + producer2(), + ) + + return if (results.allOk()) { val transformed = transform( result1.value, result2.value, @@ -154,18 +156,13 @@ public inline fun zipOrAccumulate( Ok(transformed) } else { - val errors = listOf( - result1, - result2 - ).mapNotNull { it.getError() } - - Err(errors) + Err(results.filterErrors()) } } /** - * Apply a [transformation][transform] to three [Results][Result], if all [Results][Result] are [Ok]. - * If not, the all arguments which are [Err] will be collected. + * Applies the given [transform] function to three [Results][Result], collecting any result that + * [is an error][Result.isErr] to a [List]. */ public inline fun zipOrAccumulate( producer1: () -> Result, @@ -184,11 +181,13 @@ public inline fun zipOrAccumulate( val result2 = producer2() val result3 = producer3() - return if ( - result1 is Ok && - result2 is Ok && - result3 is Ok - ) { + val results = listOf( + result1, + result2, + result3, + ) + + return if (results.allOk()) { val transformed = transform( result1.value, result2.value, @@ -197,19 +196,13 @@ public inline fun zipOrAccumulate( Ok(transformed) } else { - val errors = listOf( - result1, - result2, - result3 - ).mapNotNull { it.getError() } - - Err(errors) + Err(results.filterErrors()) } } /** - * Apply a [transformation][transform] to four [Results][Result], if all [Results][Result] are [Ok]. - * If not, the all arguments which are [Err] will be collected. + * Applies the given [transform] function to four [Results][Result], collecting any result that + * [is an error][Result.isErr] to a [List]. */ public inline fun zipOrAccumulate( producer1: () -> Result, @@ -231,35 +224,30 @@ public inline fun zipOrAccumulate( val result3 = producer3() val result4 = producer4() - return if ( - result1 is Ok && - result2 is Ok && - result3 is Ok && - result4 is Ok - ) { + val results = listOf( + result1, + result2, + result3, + result4, + ) + + return if (results.allOk()) { val transformed = transform( result1.value, result2.value, result3.value, - result4.value + result4.value, ) Ok(transformed) } else { - val errors = listOf( - result1, - result2, - result3, - result4 - ).mapNotNull { it.getError() } - - Err(errors) + Err(results.filterErrors()) } } /** - * Apply a [transformation][transform] to five [Results][Result], if all [Results][Result] are [Ok]. - * If not, the all arguments which are [Err] will be collected. + * Applies the given [transform] function to five [Results][Result], collecting any result that + * [is an error][Result.isErr] to a [List]. */ public inline fun zipOrAccumulate( producer1: () -> Result, @@ -284,31 +272,25 @@ public inline fun zipOrAccumulate( val result4 = producer4() val result5 = producer5() - return if ( - result1 is Ok && - result2 is Ok && - result3 is Ok && - result4 is Ok && - result5 is Ok - ) { + val results = listOf( + result1, + result2, + result3, + result4, + result5 + ) + + return if (results.allOk()) { val transformed = transform( result1.value, result2.value, result3.value, result4.value, - result5.value + result5.value, ) Ok(transformed) } else { - val errors = listOf( - result1, - result2, - result3, - result4, - result5 - ).mapNotNull { it.getError() } - - Err(errors) + Err(results.filterErrors()) } }