
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
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy