Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

An inline version of Result4k #35

Open
wants to merge 2 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions result4k/src/main/kotlin/dev/forkhandles/result4k/iterables.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package dev.forkhandles.result4k


@Suppress("UNCHECKED_CAST")
fun <T, E> Iterable<Result<T, E>>.allValues(): Result<List<T>, E> =
Success(map { r -> r.onFailure { return it } })
Success(map { r -> r.onFailure { return it as Result<List<T>, E> } })

fun <T, E> Iterable<Result<T, E>>.anyValues(): List<T> =
filterIsInstance<Success<T>>().map { it.value }
filter { it.isSuccess() }.map { it.unsafeValue }

fun <T, E> Iterable<Result<T, E>>.partition(): Pair<List<T>, List<E>> {
val oks = mutableListOf<T>()
val errs = mutableListOf<E>()
forEach {
when (it) {
is Success<T> -> oks.add(it.value)
is Failure<E> -> errs.add(it.reason)
when {
it.isSuccess() -> oks.add(it.unsafeValue)
else -> errs.add(it.unsafeReason)
}
}
return Pair(oks, errs)
Expand Down
12 changes: 6 additions & 6 deletions result4k/src/main/kotlin/dev/forkhandles/result4k/nullables.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ inline fun <T : Any, E> Result<T?, E>.filterNotNull(failureDescription: () -> E)
/**
* Returns the success value, or null if the Result is a failure.
*/
fun <T, E> Result<T, E>.valueOrNull() = when (this) {
is Success<T> -> value
is Failure<E> -> null
fun <T, E> Result<T, E>.valueOrNull() = when {
isSuccess() -> unsafeValue
else -> null
}

/**
* Returns the failure reason, or null if the Result is a success.
*/
fun <T, E> Result<T, E>.failureOrNull() = when (this) {
is Success<T> -> null
is Failure<E> -> reason
fun <T, E> Result<T, E>.failureOrNull() = when {
isFailure() -> unsafeReason
else -> null
}
70 changes: 49 additions & 21 deletions result4k/src/main/kotlin/dev/forkhandles/result4k/result.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,35 @@ package dev.forkhandles.result4k
/**
* A result of a computation that can succeed or fail.
*/
sealed class Result<out T, out E>
@Suppress("UNCHECKED_CAST")
@JvmInline
value class Result<out T, out E>(private val _value: Any?) {
fun isFailure(): Boolean = _value is FailureWrapper<*>
fun isSuccess(): Boolean = !isFailure()

data class Success<out T>(val value: T) : Result<T, Nothing>()
data class Failure<out E>(val reason: E) : Result<Nothing, E>()
@PublishedApi
internal val unsafeValue: T
get() = when {
isSuccess() -> _value as T
else -> error("Attempt to get value on $this")
}

@PublishedApi
internal val unsafeReason: E
get() = when {
isSuccess() -> error("Attempt to get reason on $this")
else -> (_value as FailureWrapper<E>).reason
}

internal data class FailureWrapper<F>(internal val reason: F)
}

fun <T> Success(value: T): Result<T, Nothing> = Result(value)
fun <E> Failure(failure: E): Result<Nothing, E> = Result(Result.FailureWrapper(failure))

inline val <E> Result<Nothing, E>.reason: E get() = unsafeReason

inline val <T> Result<T, Nothing>.value: T get() = unsafeValue

/**
* Call a function and wrap the result in a Result, catching any Exception and returning it as Err value.
Expand All @@ -29,19 +54,22 @@ inline fun <T, Tʹ, E> Result<T, E>.map(f: (T) -> Tʹ): Result<Tʹ, E> =
/**
* Flat-map a function over the _value_ of a successful Result.
*/
@Suppress("UNCHECKED_CAST")
inline fun <T, Tʹ, E> Result<T, E>.flatMap(f: (T) -> Result<Tʹ, E>): Result<Tʹ, E> =
when (this) {
is Success<T> -> f(value)
is Failure<E> -> this
when {
this.isSuccess() -> f(unsafeValue)
else -> this as Result<Tʹ, E>
}

/**
* Flat-map a function over the _reason_ of a unsuccessful Result.
*/
inline fun <T, E, Eʹ> Result<T, E>.flatMapFailure(f: (E) -> Result<T, Eʹ>): Result<T, Eʹ> = when (this) {
is Success<T> -> this
is Failure<E> -> f(reason)
}
@Suppress("UNCHECKED_CAST")
inline fun <T, E, Eʹ> Result<T, E>.flatMapFailure(f: (E) -> Result<T, Eʹ>): Result<T, Eʹ> =
when {
this.isFailure() -> f(unsafeReason)
else -> this as Result<T, Eʹ>
}

/**
* Map a function over the _reason_ of an unsuccessful Result.
Expand All @@ -52,25 +80,25 @@ inline fun <T, E, Eʹ> Result<T, E>.mapFailure(f: (E) -> Eʹ): Result<T, Eʹ> =
/**
* Unwrap a Result in which both the success and failure values have the same type, returning a plain value.
*/
fun <T> Result<T, T>.get() = when (this) {
is Success<T> -> value
is Failure<T> -> reason
fun <T> Result<T, T>.get() = when {
isSuccess() -> unsafeValue
else -> unsafeReason
}

/**
* Unwrap a successful result or throw an exception
*/
fun <T, X : Throwable> Result<T, X>.orThrow() = when (this) {
is Success<T> -> value
is Failure<X> -> throw reason
fun <T, X : Throwable> Result<T, X>.orThrow() = when {
this.isSuccess() -> unsafeValue
else -> throw unsafeReason
}

/**
* Unwrap a Result, by returning the success value or calling _block_ on failure to abort from the current function.
*/
inline fun <T, E> Result<T, E>.onFailure(block: (Failure<E>) -> Nothing): T = when (this) {
is Success<T> -> value
is Failure<E> -> block(this)
inline fun <T, E> Result<T, E>.onFailure(block: (Result<T, E>) -> Nothing): T = when {
this.isSuccess() -> unsafeValue
else -> block(this)
}

/**
Expand All @@ -83,11 +111,11 @@ inline fun <S, T : S, U : S, E> Result<T, E>.recover(errorToValue: (E) -> U): S
* Perform a side effect with the success value.
*/
inline fun <T, E> Result<T, E>.peek(f: (T) -> Unit) =
apply { if (this is Success<T>) f(value) }
apply { if (this.isSuccess()) f(unsafeValue) }

/**
* Perform a side effect with the failure reason.
*/
inline fun <T, E> Result<T, E>.peekFailure(f: (E) -> Unit) =
apply { if (this is Failure<E>) f(reason) }
apply { if (this.isFailure()) f(unsafeReason) }

8 changes: 4 additions & 4 deletions values4k/src/main/kotlin/dev/forkhandles/values/result4k.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import dev.forkhandles.result4k.Failure
import dev.forkhandles.result4k.Result
import dev.forkhandles.result4k.Success
import dev.forkhandles.result4k.map
import dev.forkhandles.result4k.recover
import dev.forkhandles.result4k.resultFrom

/**
Expand Down Expand Up @@ -35,9 +36,8 @@ private fun <IN, DOMAIN : Value<PRIMITIVE>, PRIMITIVE : Any> List<IN>.toResult4k
else ->
drop(1)
.fold(fn(first()).map(::listOf)) { acc, next ->
when (acc) {
is Success -> fn(next).map { acc.value + it }
is Failure -> acc
}
acc
.map { value -> fn(next).map { value + it }}
.recover { acc }
}
}