Convert Result to an inline value class

This commit is contained in:
Michael Bull 2024-03-05 20:36:03 +00:00
parent eecd1b7d99
commit 981fbe2812
3 changed files with 53 additions and 104 deletions

View File

@ -10,11 +10,6 @@ import kotlin.contracts.contract
* - Rust: [Result.ok](https://doc.rust-lang.org/std/result/enum.Result.html#method.ok)
*/
public fun <V, E> Result<V, E>.get(): V? {
contract {
returnsNotNull() implies (this@get is Ok<V>)
returns(null) implies (this@get is Err<E>)
}
return when {
isOk -> value
else -> null
@ -27,11 +22,6 @@ public fun <V, E> Result<V, E>.get(): V? {
* - Rust: [Result.err](https://doc.rust-lang.org/std/result/enum.Result.html#method.err)
*/
public fun <V, E> Result<V, E>.getError(): E? {
contract {
returns(null) implies (this@getError is Ok<V>)
returnsNotNull() implies (this@getError is Err<E>)
}
return when {
isErr -> error
else -> null
@ -111,10 +101,6 @@ public inline infix fun <V, E> Result<V, E>.getErrorOrElse(transform: (V) -> E):
* This is functionally equivalent to [`getOrElse { throw it }`][getOrElse].
*/
public fun <V, E : Throwable> Result<V, E>.getOrThrow(): V {
contract {
returns() implies (this@getOrThrow is Ok<V>)
}
return when {
isOk -> value
else -> throw error
@ -127,7 +113,6 @@ public fun <V, E : Throwable> Result<V, E>.getOrThrow(): V {
*/
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)
}

View File

@ -1,19 +1,21 @@
package com.github.michaelbull.result
import kotlin.jvm.JvmInline
/**
* Returns a [Result] that [is ok][Result.isOk] and contains a [value][Result.value].
*/
@Suppress("FunctionName", "DEPRECATION")
@Suppress("FunctionName")
public fun <V> Ok(value: V): Result<V, Nothing> {
return Ok(value, null)
return Result(value)
}
/**
* Returns a [Result] that [is an error][Result.isErr] and contains an [error][Result.error].
*/
@Suppress("FunctionName", "DEPRECATION")
@Suppress("FunctionName")
public fun <E> Err(error: E): Result<Nothing, E> {
return Err(error, null)
return Result(Failure(error))
}
/**
@ -37,94 +39,66 @@ public inline fun <V, E, F> Result<V, E>.asErr(): Result<Nothing, F> {
/**
* [Result] is a type that represents either success ([Ok]) or failure ([Err]).
*
* A [Result] that [is ok][Result.isOk] will have a [value][Result.value] of type [V], whereas a
* [Result] that [is an error][Result.isErr] will have an [error][Result.error] of type [E].
*
* - Elm: [Result](http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Result)
* - Haskell: [Data.Either](https://hackage.haskell.org/package/base-4.10.0.0/docs/Data-Either.html)
* - Rust: [Result](https://doc.rust-lang.org/std/result/enum.Result.html)
*/
public sealed class Result<out V, out E> {
@JvmInline
public value class Result<out V, out E> internal constructor(
private val inlineValue: Any?,
) {
public abstract val value: V
public abstract val error: E
@Suppress("UNCHECKED_CAST")
public val value: V
get() = inlineValue as V
public abstract val isOk: Boolean
public abstract val isErr: Boolean
@Suppress("UNCHECKED_CAST")
public val error: E
get() = (inlineValue as Failure<E>).error
public abstract operator fun component1(): V?
public abstract operator fun component2(): E?
public val isOk: Boolean
get() = inlineValue !is Failure<*>
public val isErr: Boolean
get() = inlineValue is Failure<*>
public operator fun component1(): V? {
return when {
isOk -> value
else -> null
}
}
/**
* Represents a successful [Result], containing a [value].
*/
@Deprecated(
message = "Using Ok as a return type is deprecated.",
replaceWith = ReplaceWith("Result<V, Nothing>"),
)
public class Ok<out V> internal constructor(
override val value: V,
@Suppress("UNUSED_PARAMETER") placeholder: Any?,
) : Result<V, Nothing>() {
override val error: Nothing
get() {
throw NoSuchElementException()
public operator fun component2(): E? {
return when {
isErr -> error
else -> null
}
}
override val isOk: Boolean = true
override val isErr: Boolean = false
override fun component1(): V = value
override fun component2(): Nothing? = null
override fun toString(): String {
return when {
isOk -> "Ok($value)"
else -> "Err($error)"
}
}
}
private class Failure<out E>(
val error: E,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as Ok<*>
if (value != other.value) return false
return true
return other is Failure<*> && error == other.error
}
override fun hashCode(): Int = value.hashCode()
override fun toString(): String = "Ok($value)"
override fun hashCode(): Int {
return error.hashCode()
}
/**
* Represents a failed [Result], containing an [error].
*/
@Deprecated(
message = "Using Err as a return type is deprecated.",
replaceWith = ReplaceWith("Result<Nothing, E>"),
)
public class Err<out E> internal constructor(
override val error: E,
@Suppress("UNUSED_PARAMETER") placeholder: Any?,
) : Result<Nothing, E>() {
override val value: Nothing
get() {
throw NoSuchElementException()
override fun toString(): String {
return "Failure($error)"
}
override val isOk: Boolean = false
override val isErr: Boolean = true
override fun component1(): Nothing? = null
override fun component2(): E = error
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as Err<*>
if (error != other.error) return false
return true
}
override fun hashCode(): Int = error.hashCode()
override fun toString(): String = "Err($error)"
}

View File

@ -14,10 +14,6 @@ public class UnwrapException(message: String) : Exception(message)
* @throws UnwrapException if this result [is an error][Result.isErr].
*/
public fun <V, E> Result<V, E>.unwrap(): V {
contract {
returns() implies (this@unwrap is Ok<V>)
}
return when {
isOk -> value
else -> throw UnwrapException("called Result.unwrap on an Err value $error")
@ -38,7 +34,6 @@ public fun <V, E> Result<V, E>.unwrap(): V {
public inline infix fun <V, E> Result<V, E>.expect(message: () -> Any): V {
contract {
callsInPlace(message, InvocationKind.AT_MOST_ONCE)
returns() implies (this@expect is Ok<V>)
}
return when {
@ -56,10 +51,6 @@ public inline infix fun <V, E> Result<V, E>.expect(message: () -> Any): V {
* @throws UnwrapException if this result [is ok][Result.isOk].
*/
public fun <V, E> Result<V, E>.unwrapError(): E {
contract {
returns() implies (this@unwrapError is Err<E>)
}
return when {
isErr -> error
else -> throw UnwrapException("called Result.unwrapError on an Ok value $value")
@ -80,7 +71,6 @@ public fun <V, E> Result<V, E>.unwrapError(): E {
public inline infix fun <V, E> Result<V, E>.expectError(message: () -> Any): E {
contract {
callsInPlace(message, InvocationKind.AT_MOST_ONCE)
returns() implies (this@expectError is Err<E>)
}
return when {