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

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

package arrow.data

import arrow.HK
import arrow.core.*
import arrow.higherkind
import arrow.typeclasses.Applicative
import arrow.typeclasses.Semigroup
import arrow.typeclasses.semigroup

typealias ValidatedNel = Validated, A>
typealias Valid = Validated.Valid
typealias Invalid = Validated.Invalid

/**
 * Port of https://github.com/typelevel/cats/blob/master/core/src/main/scala/cats/data/Validated.scala
 */
@higherkind sealed class Validated : ValidatedKind {

    companion object {

        fun  invalidNel(e: E): ValidatedNel = Invalid(NonEmptyList(e, listOf()))

        fun  validNel(a: A): ValidatedNel = Valid(a)

        /**
         * Converts a `Try` to a `Validated`.
         */
        fun  fromTry(t: Try): Validated = t.fold({ Invalid(it) }, { Valid(it) })

        /**
         * Converts an `Either` to an `Validated`.
         */
        fun  fromEither(e: Either): Validated = e.fold({ Invalid(it) }, { Valid(it) })

        /**
         * Converts an `Option` to an `Validated`, where the provided `ifNone` values is returned on
         * the invalid of the `Validated` when the specified `Option` is `None`.
         */
        fun  fromOption(o: Option, ifNone: () -> E): Validated =
                o.fold(
                        { Invalid(ifNone()) },
                        { Valid(it) }
                )

    }

    data class Valid(val a: A) : Validated()

    data class Invalid(val e: E) : Validated()

    inline fun  fold(fe: (E) -> B, fa: (A) -> B): B =
            when (this) {
                is Valid -> fa(a)
                is Invalid -> (fe(e))
            }

    val isValid =
            fold({ false }, { true })
    val isInvalid =
            fold({ true }, { false })

    /**
     * Is this Valid and matching the given predicate
     */
    fun exist(predicate: (A) -> Boolean): Boolean = fold({ false }, { predicate(it) })

    /**
     * Converts the value to an Either
     */
    fun toEither(): Either = fold({ Left(it) }, { Right(it) })

    /**
     * Returns Valid values wrapped in Some, and None for Invalid values
     */
    fun toOption(): Option = fold({ None }, { Some(it) })

    /**
     * Converts the value to an Ior
     */
    fun toIor(): Ior = fold({ it.leftIor() }, { it.rightIor() })

    /**
     * Convert this value to a single element List if it is Valid,
     * otherwise return an empty List
     */
    fun toList(): List = fold({ listOf() }, { listOf(it) })

    /** Lift the Invalid value into a NonEmptyList. */
    fun toValidatedNel(): ValidatedNel =
            fold(
                    { invalidNel(it) },
                    { Valid(it) }
            )

    /**
     * Convert to an Either, apply a function, convert back. This is handy
     * when you want to use the Monadic properties of the Either type.
     */
    fun  withEither(f: (Either) -> Either): Validated = fromEither(f(toEither()))

    /**
     * Validated is a [[functor.Bifunctor]], this method applies one of the
     * given functions.
     */
    fun  bimap(fe: (E) -> EE, fa: (A) -> AA): Validated = fold({ Invalid(fe(it)) }, { Valid(fa(it)) })

    /**
     * Apply a function to a Valid value, returning a new Valid value
     */
    fun  map(f: (A) -> B): Validated = bimap({ it }, { f(it) })

    /**
     * Apply a function to an Invalid value, returning a new Invalid value.
     * Or, if the original valid was Valid, return it.
     */
    fun  leftMap(f: (E) -> EE): Validated = bimap({ f(it) }, { it })

    /**
     * apply the given function to the value with the given B when
     * valid, otherwise return the given B
     */
    fun  foldLeft(b: B, f: (B, A) -> B): B = fold({ b }, { f(b, it) })

    fun swap(): Validated = fold({ Valid(it) }, { Invalid(it) })
}

/**
 * Return the Valid value, or the default if Invalid
 */
fun  Validated.getOrElse(default: () -> B): B = fold({ default() }, { it })

/**
 * Return the Valid value, or the result of f if Invalid
 */
fun  Validated.valueOr(f: (E) -> B): B = fold({ f(it) }, { it })

/**
 * If `this` is valid return `this`, otherwise if `that` is valid return `that`, otherwise combine the failures.
 * This is similar to [[orElse]] except that here failures are accumulated.
 */
fun  Validated.findValid(SE: Semigroup, that: () -> Validated): Validated =
        fold(

                { e ->
                    that().fold(
                            { ee -> Invalid(SE.combine(e, ee)) },
                            { Valid(it) }
                    )
                },
                { Valid(it) }
        )

/**
 * Return this if it is Valid, or else fall back to the given default.
 * The functionality is similar to that of [[findValid]] except for failure accumulation,
 * where here only the error on the right is preserved and the error on the left is ignored.
 */
fun  Validated.orElse(default: () -> Validated): Validated =
        fold(
                { default() },
                { Valid(it) }
        )

/**
 * From Apply:
 * if both the function and this value are Valid, apply the function
 */
fun  Validated.ap(f: Validated B>, SE: Semigroup): Validated =
        when (this) {
            is Valid -> f.fold({ Invalid(it) }, { Valid(it(a)) })
            is Invalid -> f.fold({ Invalid(SE.combine(it, e)) }, { Invalid(e) })
        }

fun  Validated.handleLeftWith(f: (E) -> ValidatedKind): Validated =
        fold({ f(it).ev() }, { Valid(it) })

fun  Validated.foldRight(lb: Eval, f: (A, Eval) -> Eval): Eval =
        when (this) {
            is Valid -> f(this.a, lb)
            is Invalid -> lb
        }

fun  Validated.traverse(f: (A) -> HK, GA: Applicative): HK> =
        when (this) {
            is Valid -> GA.map(f(this.a), { Valid(it) })
            is Invalid -> GA.pure(this)
        }

inline fun  Validated.combine(y: ValidatedKind,
                                                          SE: Semigroup = semigroup(),
                                                          SA: Semigroup = semigroup()): Validated =
        y.ev().let { that ->
            when {
                this is Valid && that is Valid -> Valid(SA.combine(this.a, that.a))
                this is Invalid && that is Invalid -> Invalid(SE.combine(this.e, that.e))
                this is Invalid -> this
                else -> that
            }
        }

fun  Validated.combineK(y: ValidatedKind, SE: Semigroup): Validated {
    val xev = this
    val yev = y.ev()
    return when (xev) {
        is Valid -> xev
        is Invalid -> when (yev) {
            is Invalid -> Invalid(SE.combine(xev.e, yev.e))
            is Valid -> yev
        }
    }
}