com.ubertob.kondor.outcome.Outcome.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kondor-outcome Show documentation
Show all versions of kondor-outcome Show documentation
A Kotlin specialized Either type for wrapping results in a functional way
package com.ubertob.kondor.outcome
sealed interface Outcome {
fun transform(f: (T) -> U): Outcome =
when (this) {
is Success -> f(this.value).asSuccess()
is Failure -> this
}
fun transformFailure(f: (E) -> F): Outcome =
when (this) {
is Success -> this
is Failure -> f(this.error).asFailure()
}
fun orThrow(): T =
when (this) {
is Success -> value
is Failure -> throw OutcomeException(error)
}
fun orNull(): T? = recover { null }
companion object {
fun transform2(
first: Outcome,
second: Outcome,
f: (T, U) -> R
): Outcome = f `!` first `*` second
inline fun tryOrFail(block: () -> T): Outcome =
try {
block().asSuccess()
} catch (t: Throwable) {
ThrowableError(t).asFailure()
}
}
}
@JvmInline
value class Success internal constructor(val value: T) : Outcome
@JvmInline
value class Failure internal constructor(val error: E) : Outcome
inline fun Outcome.castOrFail(error: (T) -> @UnsafeVariance E): Outcome =
when (this) {
is Failure -> this
is Success -> if (value is U) {
value.asSuccess()
} else {
error(value).asFailure()
}
}
inline fun Outcome.recover(recoverError: (E) -> T): T =
when (this) {
is Success -> value
is Failure -> recoverError(error)
}
inline fun Outcome.bind(f: (T) -> Outcome): Outcome =
when (this) {
is Success -> f(value)
is Failure -> this
}
inline fun Outcome.bindFailure(f: (E) -> Outcome): Outcome =
when (this) {
is Success -> this
is Failure -> f(error)
}
fun Outcome>.join(): Outcome =
bind { it }
fun Outcome.combine(other: Outcome): Outcome> =
bind { first -> other.transform { second -> first to second } }
//Kleisli composition
infix fun ((A) -> Outcome).compose(other: (B) -> Outcome): (A) -> Outcome =
{ a -> this(a).bind(other) }
//convenience methods
fun Outcome.failIf(predicate: (T) -> Boolean, error: (T) -> E): Outcome =
failUnless({ predicate(this).not() }, error)
fun Outcome.failUnless(predicate: T.() -> Boolean, error: (T) -> E): Outcome =
when (this) {
is Success -> if (predicate(value)) this else error(value).asFailure()
is Failure -> this
}
fun Outcome.failIfNull(error: () -> E): Outcome =
when (this) {
is Success -> value?.asSuccess() ?: error().asFailure()
is Failure -> this
}
fun Outcome.transformIfNotNull(f: (T) -> U): Outcome =
transform { value ->
value?.let(f)
}
inline fun Outcome.onFailure(exitBlock: (E) -> Nothing): T =
when (this) {
is Success -> value
is Failure -> exitBlock(error)
}
interface OutcomeError {
val msg: String
}
data class OutcomeException(val error: OutcomeError) : RuntimeException() {
override val message: String = error.msg
}
fun E.asFailure(): Outcome = Failure(this)
fun T.asSuccess(): Outcome = Success(this)
fun T?.failIfNull(error: () -> E): Outcome = this?.asSuccess() ?: error().asFailure()
fun T.asOutcome(isSuccess: T.() -> Boolean, error: (T) -> E): Outcome =
if (isSuccess(this)) asSuccess() else error(this).asFailure()
data class ThrowableError(val throwable: Throwable) : OutcomeError {
override val msg: String = throwable.message.orEmpty()
}
data class MessageError(override val msg: String) : OutcomeError
fun String.asFailure() = MessageError(this).asFailure()
// fold until end of sequence (success) or first failure
fun Iterable.foldOutcome(
initial: U,
operation: (acc: U, T) -> Outcome
): Outcome =
fold(initial.asSuccess() as Outcome) { acc, el -> acc.bind { operation(it, el) } }
fun Iterable.foldOutcomeIndexed(
initial: U,
operation: (Int, acc: U, T) -> Outcome
): Outcome =
foldIndexed(initial.asSuccess() as Outcome) { index, acc, el -> acc.bind { operation(index, it, el) } }
fun Iterable.traverse(f: (T) -> Outcome): Outcome> =
foldOutcome(ArrayList(128)) { acc, e ->
f(e).transform { acc.add(it); acc }
}
fun Iterable.traverseIndexed(f: (index: Int, T) -> Outcome): Outcome> =
foldOutcomeIndexed(mutableListOf()) { index, acc, e ->
f(index, e).transform { acc.add(it); acc }
}
fun Iterable>.extractList(): Outcome> =
traverse { it }
fun Iterable.traverseToSet(f: (T) -> Outcome): Outcome> =
foldOutcome(HashSet(128)) { acc, e ->
f(e).transform { acc.add(it); acc }
}
fun Iterable>.extractSet(): Outcome> =
traverseToSet { it }
fun Sequence.traverse(f: (T) -> Outcome): Outcome> =
foldOutcome(ArrayList(128)) { acc, e ->
f(e).transform { acc.add(it); acc }
}
fun Sequence>.extractList(): Outcome> =
traverse { it }
fun Sequence.traverseToSet(f: (T) -> Outcome): Outcome> =
foldOutcome(HashSet(128)) { acc, e ->
f(e).transform { acc.add(it); acc }
}
fun Sequence>.extractSet(): Outcome> =
traverseToSet { it }
// fold until end of sequence (success) or first failure
fun Sequence.foldOutcome(
initial: U,
operation: (acc: U, T) -> Outcome
): Outcome {
val iter = iterator()
tailrec fun loop(acc: U): Outcome =
if (!iter.hasNext()) acc.asSuccess()
else when (val el = operation(acc, iter.next())) {
is Failure -> el
is Success -> loop(el.value)
}
return loop(initial)
}
infix fun ((A, B) -> D).`!`(other: Outcome): Outcome D> =
other.transform { a -> { b -> this(a, b) } }
infix fun ((A, B, C) -> D).`!`(other: Outcome): Outcome (C) -> D> =
other.transform { a -> { b -> { c -> this(a, b, c) } } }
infix fun ((A, B, C, D) -> E).`!`(other: Outcome): Outcome (C) -> (D) -> E> =
other.transform { a -> { b -> { c -> { d -> this(a, b, c, d) } } } }
infix fun ((A, B, C, D, E) -> F).`!`(other: Outcome): Outcome (C) -> (D) -> (E) -> F> =
other.transform { a -> { b -> { c -> { d -> { e -> this(a, b, c, d, e) } } } } }
@Suppress("DANGEROUS_CHARACTERS")
infix fun Outcome B>.`*`(a: Outcome): Outcome =
bind { a.transform(it) }
infix fun ((A, B) -> D).`!`(other: () -> Outcome): Outcome D> =
other().transform { a -> { b -> this(a, b) } }
infix fun ((A, B, C) -> D).`!`(other: () -> Outcome): Outcome (C) -> D> =
other().transform { a -> { b -> { c -> this(a, b, c) } } }
@Suppress("DANGEROUS_CHARACTERS")
infix fun Outcome B>.`*`(a: () -> Outcome): Outcome =
bind { a().transform(it) }
//for side effects
fun Outcome.withSuccess(block: (T) -> Unit): Outcome =
transform { it.also(block) }
fun Outcome.withFailure(block: (E) -> Unit): Outcome =
transformFailure { it.also(block) }
fun Outcome.bindAlso(f: (T) -> Outcome): Outcome =
bind { value -> f(value).transform { value } }
//for operating with collections inside Outcome
fun Outcome>.map(f: (T) -> U): Outcome> =
transform { it.map(f) }
fun Outcome>.filter(f: (T) -> Boolean): Outcome> =
transform { it.filter(f) }
fun Outcome>.flatMap(f: (T) -> Outcome): Outcome> =
bind { it.traverse(f) }
© 2015 - 2024 Weber Informatics LLC | Privacy Policy