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

arrow.data.Try.kt Maven / Gradle / Ivy

There is a newer version: 0.8.2
Show newest version
package arrow.data

import arrow.*
import arrow.core.*
import arrow.legacy.*
import arrow.typeclasses.Applicative

typealias Failure = Try.Failure
typealias Success = Try.Success

/**
 * The `Try` type represents a computation that may either result in an exception, or return a
 * successfully computed value.
 *
 * Port of https://github.com/scala/scala/blob/v2.12.1/src/library/scala/util/Try.scala
 */
@higherkind
sealed class Try : TryKind {

    companion object {

        fun  pure(a: A): Try = Success(a)

        tailrec fun  tailRecM(a: A, f: (A) -> TryKind>): Try {
            val ev: Try> = f(a).ev()
            return when (ev) {
                is Failure -> Failure(ev.exception).ev()
                is Success -> {
                    val b: Either = ev.value
                    when (b) {
                        is Either.Left -> tailRecM(b.a, f)
                        is Either.Right -> Success(b.b)
                    }
                }
            }

        }

        inline operator fun  invoke(f: () -> A): Try =
                try {
                    Success(f())
                } catch (e: Throwable) {
                    Failure(e)
                }

        fun  raise(e: Throwable): Try = Failure(e)

    }

    @Deprecated(DeprecatedUnsafeAccess, ReplaceWith("getOrElse { ifEmpty }"))
    operator fun invoke() = get()

    fun  traverse(f: (A) -> HK, GA: Applicative): HK> =
            this.ev().fold({ GA.pure(raise(IllegalStateException())) }, { GA.map(f(it), { Try { it } }) })

    fun  ap(ff: TryKind<(A) -> B>): Try = ff.ev().flatMap { f -> map(f) }.ev()

    /**
     * Returns the given function applied to the value from this `Success` or returns this if this is a `Failure`.
     */
    inline fun  flatMap(crossinline f: (A) -> TryKind): Try = fold({ raise(it) }, { f(it).ev() })

    /**
     * Maps the given function to the value from this `Success` or returns this if this is a `Failure`.
     */
    inline fun  map(crossinline f: (A) -> B): Try = fold({ Failure(it) }, { Success(f(it)) })

    /**
     * Converts this to a `Failure` if the predicate is not satisfied.
     */
    inline fun filter(crossinline p: Predicate): Try =
            fold(
                    { Failure(it) },
                    { if (p(it)) Success(it) else Failure(TryException.PredicateException("Predicate does not hold for $it")) }
            )

    /**
     * Inverts this `Try`. If this is a `Failure`, returns its exception wrapped in a `Success`.
     * If this is a `Success`, returns a `Failure` containing an `UnsupportedOperationException`.
     */
    fun failed(): Try =
            fold(
                    { Success(it) },
                    { Failure(TryException.UnsupportedOperationException("Success")) }
            )

    /**
     * Applies `fa` if this is a `Failure` or `fb` if this is a `Success`.
     * If `fb` is initially applied and throws an exception,
     * then `fa` is applied with this exception.
     */
    inline fun  fold(fa: (Throwable) -> B, fb: (A) -> B): B =
            when (this) {
                is Failure -> fa(exception)
                is Success -> try {
                    fb(value)
                } catch (e: Throwable) {
                    fa(e)
                }
            }

    abstract fun isFailure(): Boolean

    abstract fun isSuccess(): Boolean

    @Deprecated(DeprecatedUnsafeAccess, ReplaceWith("fold({ Unit }, f)"))
    fun foreach(f: (A) -> Unit) {
        if (isSuccess()) f(get())
    }

    @Deprecated(DeprecatedUnsafeAccess, ReplaceWith("map { f(it); it }"))
    fun onEach(f: (A) -> Unit): Try = map {
        f(it)
        it
    }

    fun exists(predicate: Predicate): Boolean = fold({ false }, { predicate(it) })

    @Deprecated(DeprecatedUnsafeAccess, ReplaceWith("getOrElse { ifEmpty }"))
    abstract fun get(): A

    @Deprecated(DeprecatedUnsafeAccess, ReplaceWith("map { body(it); it }"))
    fun onSuccess(body: (A) -> Unit): Try {
        foreach(body)
        return this
    }

    @Deprecated(DeprecatedUnsafeAccess, ReplaceWith("fold ({ Try { body(it); it }}, { Try.pure(it) })"))
    fun onFailure(body: (Throwable) -> Unit): Try = when (this) {
        is Success -> this
        is Failure -> {
            body(exception)
            this
        }
    }

    fun toOption(): Option = fold({ None }, { Some(it) })

    fun toEither(): Either = fold({ Left(it) }, { Right(it) })

    @Deprecated("arrow.data.Either is already right biased. This function will be removed in future releases", ReplaceWith("toEither()"))
    fun toDisjunction(): Disjunction = toEither().toDisjunction()

    /**
     * The `Failure` type represents a computation that result in an exception.
     */
    data class Failure(val exception: Throwable) : Try() {
        override fun isFailure(): Boolean = true

        override fun isSuccess(): Boolean = false

        override fun get(): A {
            throw exception
        }
    }

    /**
     * The `Success` type represents a computation that return a successfully computed value.
     */
    data class Success(val value: A) : Try() {
        override fun isFailure(): Boolean = false

        override fun isSuccess(): Boolean = true

        override fun get(): A = value
    }
}

sealed class TryException(override val message: String) : kotlin.Exception(message) {
    data class PredicateException(override val message: String) : TryException(message)
    data class UnsupportedOperationException(override val message: String) : TryException(message)
}

fun  Try.foldLeft(b: B, f: (B, A) -> B): B = this.ev().fold({ b }, { f(b, it) })

fun  Try.foldRight(lb: Eval, f: (A, Eval) -> Eval): Eval = this.ev().fold({ lb }, { f(it, lb) })

/**
 * Returns the value from this `Success` or the given `default` argument if this is a `Failure`.
 *
 * ''Note:'': This will throw an exception if it is not a success and default throws an exception.
 */
fun  Try.getOrDefault(default: () -> B): B = fold({ default() }, { it })

/**
 * Returns the value from this `Success` or the given `default` argument if this is a `Failure`.
 *
 * ''Note:'': This will throw an exception if it is not a success and default throws an exception.
 */
fun  Try.getOrElse(default: (Throwable) -> B): B = fold(default, { it })

fun  Try.orElse(f: () -> Try): Try = when (this) {
    is Try.Success -> this
    is Try.Failure -> f()
}

/**
 * Applies the given function `f` if this is a `Failure`, otherwise returns this if this is a `Success`.
 * This is like `flatMap` for the exception.
 */
fun  Try.recoverWith(f: (Throwable) -> Try): Try = fold({ f(it) }, { Success(it) })

@Deprecated(DeprecatedAmbiguity, ReplaceWith("recoverWith(f)"))
fun  Try.rescue(f: (Throwable) -> Try): Try = recoverWith(f)

/**
 * Applies the given function `f` if this is a `Failure`, otherwise returns this if this is a `Success`.
 * This is like map for the exception.
 */
fun  Try.recover(f: (Throwable) -> B): Try = fold({ Success(f(it)) }, { Success(it) })

@Deprecated(DeprecatedAmbiguity, ReplaceWith("recover(f)"))
fun  Try.handle(f: (Throwable) -> A): Try = recover(f)

/**
 * Completes this `Try` by applying the function `f` to this if this is of type `Failure`,
 * or conversely, by applying `s` if this is a `Success`.
 */
fun  Try.transform(s: (A) -> Try, f: (Throwable) -> Try): Try = fold({ f(it) }, { flatMap(s) })

fun  (() -> A).try_(): Try = Try(this)

fun  Try>.flatten(): Try = flatMap(::identity)