All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ubertob.kondor.outcome.Outcome.kt Maven / Gradle / Ivy

There is a newer version: 3.2.3
Show newest version
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