//arrow-core/arrow.core/Validated

Validated

common sealed class Validated<out E, out A>

Imagine you are filling out a web form to sign up for an account. You input your username and password, then submit. A response comes back saying your username can’t have dashes in it, so you make some changes, then resubmit. You can’t have special characters either. Change, resubmit. Password needs to have at least one capital letter. Change, resubmit. Password needs to have at least one number.

Or perhaps you’re reading from a configuration file. One could imagine the configuration library you’re using returns an Either. Your parsing may look something like:

import arrow.core.Either
import arrow.core.Either.Left
import arrow.core.flatMap

//sampleStart
data class ConnectionParams(val url: String, val port: Int)

fun <A> config(key: String): Either<String, A> = Left(key)

config<String>("url").flatMap { url ->
 config<Int>("port").map { ConnectionParams(url, it) }
}
//sampleEnd
// Either.Left(url)

You run your program and it says key “url” not found. Turns out the key was “endpoint.” So you change your code and re-run. Now it says the “port” key was not a well-formed integer.

It would be nice to have all of these errors reported simultaneously. The username’s inability to have dashes can be validated separately from it not having special characters, as well as from the password needing to have certain requirements. A misspelled (or missing) field in a config can be validated separately from another field not being well-formed.

Enter Validated.

Parallel Validation

Our goal is to report any and all errors across independent bits of data. For instance, when we ask for several pieces of configuration, each configuration field can be validated separately from one another. How then do we ensure that the data we are working with is independent? We ask for both of them up front.

As our running example, we will look at config parsing. Our config will be represented by a Map<String, String>. Parsing will be handled by a Read type class - we provide instances only for String and Int for brevity.

//sampleStart
abstract class Read<A> {

abstract fun read(s: String): A?

 companion object {

  val stringRead: Read<String> =
   object: Read<String>() {
    override fun read(s: String): String? = s
   }

  val intRead: Read<Int> =
   object: Read<Int>() {
    override fun read(s: String): Int? =
     if (s.matches(Regex("-?[0-9]+"))) s.toInt() else null
   }
 }
}
//sampleEnd

Then we enumerate our errors. When asking for a config value, one of two things can go wrong: The field is missing, or it is not well-formed with regards to the expected type.

sealed class ConfigError {
 data class MissingConfig(val field: String): ConfigError()
 data class ParseConfig(val field: String): ConfigError()
}

We need a data type that can represent either a successful value (a parsed configuration), or an error. It would look like the following, which Arrow provides in arrow.Validated:

sealed class Validated<out E, out A> {
 data class Valid<out A>(val a: A) : Validated<Nothing, A>()
 data class Invalid<out E>(val e: E) : Validated<E, Nothing>()
}

Now we are ready to write our parser.

import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.Validated
import arrow.core.valid
import arrow.core.invalid

//sampleStart
data class Config(val map: Map<String, String>) {
 fun <A> parse(read: Read<A>, key: String): Validated<ConfigError, A> {
  val v = map[key]
  return when (v) {
   null -> Validated.Invalid(ConfigError.MissingConfig(key))
   else ->
    when (val s = read.read(v)) {
     null -> ConfigError.ParseConfig(key).invalid()
     else -> s.valid()
    }
  }
 }
}
//sampleEnd

And, as you can see, the parser runs sequentially: it first tries to get the map value and then tries to read it. It’s then straightforward to translate this to an effect block. We use here the either block which includes syntax to obtain A from values of Validated<*, A> through the arrow.core.computations.EitherEffect.invoke

import arrow.core.Validated
import arrow.core.computations.either
import arrow.core.valid
import arrow.core.invalid

//sampleStart
data class Config(val map: Map<String, String>) {
  suspend fun <A> parse(read: Read<A>, key: String) = either<ConfigError, A> {
    val value = Validated.fromNullable(map[key]) {
      ConfigError.MissingConfig(key)
    }.bind()
    val readVal = Validated.fromNullable(read.read(value)) {
      ConfigError.ParseConfig(key)
    }.bind()
    readVal
  }
}
//sampleEnd

Everything is in place to write the parallel validator. Remember that we can only do parallel validation if each piece is independent. How do we ensure the data is independent? By asking for all of it up front. Let’s start with two pieces of data.

import arrow.core.Validated
//sampleStart
fun <E, A, B, C> parallelValidate(v1: Validated<E, A>, v2: Validated<E, B>, f: (A, B) -> C): Validated<E, C> {
 return when {
  v1 is Validated.Valid && v2 is Validated.Valid -> Validated.Valid(f(v1.value, v2.value))
  v1 is Validated.Valid && v2 is Validated.Invalid -> v2
  v1 is Validated.Invalid && v2 is Validated.Valid -> v1
  v1 is Validated.Invalid && v2 is Validated.Invalid -> TODO()
  else -> TODO()
 }
}
//sampleEnd

We’ve run into a problem. In the case where both have errors, we want to report both. We don’t have a way to combine ConfigErrors. But, as clients, we can change our Validated values where the error can be combined, say, a List<ConfigError>. We are going to use a NonEmptyList<ConfigError>—the NonEmptyList statically guarantees we have at least one value, which aligns with the fact that, if we have an Invalid, then we most certainly have at least one error. This technique is so common there is a convenient method on Validated called toValidatedNel that turns any Validated<E, A> value to a Validated<NonEmptyList<E>, A>. Additionally, the type alias ValidatedNel<E, A> is provided.

Time to validate:

import arrow.core.NonEmptyList
import arrow.core.Validated
//sampleStart
fun <E, A, B, C> parallelValidate
  (v1: Validated<E, A>, v2: Validated<E, B>, f: (A, B) -> C): Validated<NonEmptyList<E>, C> =
 when {
  v1 is Validated.Valid && v2 is Validated.Valid -> Validated.Valid(f(v1.value, v2.value))
  v1 is Validated.Valid && v2 is Validated.Invalid -> v2.toValidatedNel()
  v1 is Validated.Invalid && v2 is Validated.Valid -> v1.toValidatedNel()
  v1 is Validated.Invalid && v2 is Validated.Invalid -> Validated.Invalid(NonEmptyList(v1.value, listOf(v2.value)))
  else -> throw IllegalStateException("Not possible value")
 }
//sampleEnd

Improving the validation

Kotlin says that our match is not exhaustive and we have to add else. To solve this, we would need to nest our when, but that would complicate the code. To achieve this, Arrow provides zip. This function combines Validateds by accumulating errors in a tuple, which we can then map. The above function can be rewritten as follows:

import arrow.core.Validated
import arrow.core.validNel
import arrow.core.zip
import arrow.typeclasses.Semigroup

//sampleStart
val parallelValidate =
   1.validNel().zip(Semigroup.nonEmptyList<ConfigError>(), 2.validNel())
    { a, b -> /* combine the result */}
//sampleEnd

Note that there are multiple zip functions with more arities, so we could easily add more parameters without worrying about the function blowing up in complexity.

When working with NonEmptyList in the Invalid side, there is no need to supply Semigroup as shown in the example above.

import arrow.core.Validated
import arrow.core.validNel
import arrow.core.zip

//sampleStart
val parallelValidate =
  1.validNel().zip(2.validNel())
    { a, b -> /* combine the result */}
//sampleEnd

Coming back to our example, when no errors are present in the configuration, we get a ConnectionParams wrapped in a Valid instance.

import arrow.core.Validated
import arrow.core.computations.either
import arrow.core.valid
import arrow.core.invalid
import arrow.core.NonEmptyList
import arrow.typeclasses.Semigroup

data class ConnectionParams(val url: String, val port: Int)

abstract class Read<A> {
 abstract fun read(s: String): A?

 companion object {

  val stringRead: Read<String> =
   object : Read<String>() {
    override fun read(s: String): String? = s
   }

  val intRead: Read<Int> =
   object : Read<Int>() {
    override fun read(s: String): Int? =
     if (s.matches(Regex("-?[0-9]+"))) s.toInt() else null
   }
 }
}

sealed class ConfigError {
 data class MissingConfig(val field: String) : ConfigError()
 data class ParseConfig(val field: String) : ConfigError()
}

data class Config(val map: Map<String, String>) {
  suspend fun <A> parse(read: Read<A>, key: String) = either<ConfigError, A> {
    val value = Validated.fromNullable(map[key]) {
      ConfigError.MissingConfig(key)
    }.bind()
    val readVal = Validated.fromNullable(read.read(value)) {
      ConfigError.ParseConfig(key)
    }.bind()
    readVal
  }.toValidatedNel()
}


suspend fun main() {
//sampleStart
 val config = Config(mapOf("url" to "127.0.0.1", "port" to "1337"))

 val valid = config.parse(Read.stringRead, "url").zip(
   Semigroup.nonEmptyList<ConfigError>(),
   config.parse(Read.intRead, "port")
 ) { url, port -> ConnectionParams(url, port) }
//sampleEnd
 println("valid = $valid")
}

But what happens when we have one or more errors? They are accumulated in a NonEmptyList wrapped in an Invalid instance.

import arrow.core.Validated
import arrow.core.computations.either
import arrow.core.valid
import arrow.core.invalid
import arrow.core.NonEmptyList
import arrow.typeclasses.Semigroup

data class ConnectionParams(val url: String, val port: Int)

abstract class Read<A> {
 abstract fun read(s: String): A?

 companion object {

  val stringRead: Read<String> =
   object : Read<String>() {
    override fun read(s: String): String? = s
   }

  val intRead: Read<Int> =
   object : Read<Int>() {
    override fun read(s: String): Int? =
     if (s.matches(Regex("-?[0-9]+"))) s.toInt() else null
   }
 }
}

sealed class ConfigError {
 data class MissingConfig(val field: String) : ConfigError()
 data class ParseConfig(val field: String) : ConfigError()
}

data class Config(val map: Map<String, String>) {
  suspend fun <A> parse(read: Read<A>, key: String) = either<ConfigError, A> {
    val value = Validated.fromNullable(map[key]) {
      ConfigError.MissingConfig(key)
    }.bind()
    val readVal = Validated.fromNullable(read.read(value)) {
      ConfigError.ParseConfig(key)
    }.bind()
    readVal
  }.toValidatedNel()
}

suspend fun main() {
//sampleStart
val config = Config(mapOf("wrong field" to "127.0.0.1", "port" to "not a number"))

val valid = config.parse(Read.stringRead, "url").zip(
 Semigroup.nonEmptyList<ConfigError>(),
 config.parse(Read.intRead, "port")
) { url, port -> ConnectionParams(url, port) }
//sampleEnd
 println("valid = $valid")
}

Sequential Validation

If you do want error accumulation, but occasionally run into places where sequential validation is needed, then Validated provides a withEither method to allow you to temporarily turn a Validated instance into an Either instance and apply it to a function.

import arrow.core.Either
import arrow.core.flatMap
import arrow.core.left
import arrow.core.right
import arrow.core.Validated
import arrow.core.computations.either
import arrow.core.valid
import arrow.core.invalid

abstract class Read<A> {
 abstract fun read(s: String): A?

 companion object {

  val stringRead: Read<String> =
   object : Read<String>() {
    override fun read(s: String): String? = s
   }

  val intRead: Read<Int> =
   object : Read<Int>() {
    override fun read(s: String): Int? =
     if (s.matches(Regex("-?[0-9]+"))) s.toInt() else null
   }
 }
}

data class Config(val map: Map<String, String>) {
  suspend fun <A> parse(read: Read<A>, key: String) = either<ConfigError, A> {
    val value = Validated.fromNullable(map[key]) {
      ConfigError.MissingConfig(key)
    }.bind()
    val readVal = Validated.fromNullable(read.read(value)) {
      ConfigError.ParseConfig(key)
    }.bind()
    readVal
  }.toValidatedNel()
}

sealed class ConfigError {
 data class MissingConfig(val field: String) : ConfigError()
 data class ParseConfig(val field: String) : ConfigError()
}

//sampleStart
fun positive(field: String, i: Int): Either<ConfigError, Int> =
 if (i >= 0) i.right()
 else ConfigError.ParseConfig(field).left()

val config = Config(mapOf("house_number" to "-42"))

suspend fun main() {
  val houseNumber = config.parse(Read.intRead, "house_number").withEither { either ->
    either.flatMap { positive("house_number", it) }
  }
//sampleEnd
 println(houseNumber)
}

Types

Name Summary
Companion common object Companion
Invalid common data class Invalid<out E>(value: E) : Validated<E, Nothing>
Valid common data class Valid<out A>(value: A) : Validated<Nothing, A>

Functions

Name Summary
all common inline fun all(predicate: (A) -> Boolean): Boolean
bifoldLeft common inline fun <B> bifoldLeft(c: B, fe: (B, E) -> B, fa: (B, A) -> B): B
bifoldMap common inline fun <B> bifoldMap(MN: Monoid<B>, g: (E) -> B, f: (A) -> B): B
bimap common inline fun <EE, B> bimap(fe: (E) -> EE, fa: (A) -> B): Validated<EE, B>
From arrow.typeclasses.
bitraverse common inline fun <EE, B> bitraverse(fe: (E) -> Iterable<EE>, fa: (A) -> Iterable<B>): List<Validated<EE, B»
bitraverseEither common inline fun <EE, B, C> bitraverseEither(fe: (E) -> Either<EE, B>, fa: (A) -> Either<EE, C>): Either<EE, Validated<B, C»
bitraverseOption common inline fun <B, C> bitraverseOption(fe: (E) -> Option<B>, fa: (A) -> Option<C>): Option<Validated<B, C»
exist common inline fun exist(predicate: (A) -> Boolean): Boolean
Is this Valid and matching the given predicate
findOrNull common inline fun findOrNull(predicate: (A) -> Boolean): A?
fold common inline fun <B> fold(fe: (E) -> B, fa: (A) -> B): B
foldLeft common inline fun <B> foldLeft(b: B, f: (B, A) -> B): B
apply the given function to the value with the given B when valid, otherwise return the given B
foldMap common inline fun <B> foldMap(MB: Monoid<B>, f: (A) -> B): B
isEmpty common fun isEmpty(): Boolean
isNotEmpty common fun isNotEmpty(): Boolean
map common inline fun <B> map(f: (A) -> B): Validated<E, B>
Apply a function to a Valid value, returning a new Valid value
mapLeft common inline fun <EE> mapLeft(f: (E) -> EE): Validated<EE, A>
Apply a function to an Invalid value, returning a new Invalid value.
swap common fun swap(): Validated<A, E>
tap common inline fun tap(f: (A) -> Unit): Validated<E, A>
The given function is applied as a fire and forget effect if this is Valid.
tapInvalid common inline fun tapInvalid(f: (E) -> Unit): Validated<E, A>
The given function is applied as a fire and forget effect if this is Invalid.
toEither common fun toEither(): Either<E, A>
Converts the value to an Either<E, A>
toList common fun toList(): List<A>
Convert this value to a single element List if it is Valid, otherwise return an empty List
toOption common fun toOption(): Option<A>
Returns Valid values wrapped in Some, and None for Invalid values
toString common open override fun toString(): String
toValidatedNel common fun toValidatedNel(): ValidatedNel<E, A>
Lift the Invalid value into a NonEmptyList.
traverse common inline fun <B> traverse(fa: (A) -> Iterable<B>): List<Validated<E, B»
traverseEither common inline fun <EE, B> traverseEither(fa: (A) -> Either<EE, B>): Either<EE, Validated<E, B»
traverseOption common inline fun <B> traverseOption(fa: (A) -> Option<B>): Option<Validated<E, B»
void common fun void(): Validated<E, Unit>
Discards the A value inside Validated signaling this container may be pointing to a noop or an effect whose return value is deliberately ignored.
withEither common inline fun <EE, B> withEither(f: (Either<E, A>) -> Either<EE, B>): Validated<EE, B>
Convert to an Either, apply a function, convert back.

Properties

Name Summary
isInvalid common val isInvalid: Boolean
isValid common val isValid: Boolean

Inheritors

Name
Validated
Validated

Extensions

Name Summary
attempt common fun <E, A> Validated<E, A>.attempt(): Validated<Nothing, Either<E, A»
bind common fun <A> Validated<Throwable, A>.bind(): A
bisequence common fun <E, A> Validated<Iterable<E>, Iterable<A».bisequence(): List<Validated<E, A»
bisequenceEither common fun <E, A, B> Validated<Either<E, A>, Either<E, B».bisequenceEither(): Either<E, Validated<A, B»
bisequenceOption common fun <A, B> Validated<Option<A>, Option<B».bisequenceOption(): Option<Validated<A, B»
combine common fun <E, A> Validated<E, A>.combine(SE: Semigroup<E>, SA: Semigroup<A>, y: Validated<E, A>): Validated<E, A>
combineAll common fun <E, A> Validated<E, A>.combineAll(MA: Monoid<A>): A
combineK common fun <E, A> Validated<E, A>.combineK(SE: Semigroup<E>, y: Validated<E, A>): Validated<E, A>
compareTo common operator fun <E : Comparable<E>, A : Comparable<A» Validated<E, A>.compareTo(other: Validated<E, A>): Int
findValid common inline fun <E, A> Validated<E, A>.findValid(SE: Semigroup<E>, that: () -> Validated<E, A>): Validated<E, A>
If this is valid return this, otherwise if that is valid return that, otherwise combine the failures.
fold common fun <E, A> Validated<E, A>.fold(MA: Monoid<A>): A
getOrElse common inline fun <E, A> Validated<E, A>.getOrElse(default: () -> A): A
Return the Valid value, or the default if Invalid
handleError common inline fun <E, A> Validated<E, A>.handleError(f: (E) -> A): Validated<Nothing, A>
handleErrorWith common inline fun <E, A> Validated<E, A>.handleErrorWith(f: (E) -> Validated<E, A>): Validated<E, A>
leftWiden common fun <EE, E : EE, A> Validated<E, A>.leftWiden(): Validated<EE, A>
orElse common inline fun <E, A> Validated<E, A>.orElse(default: () -> Validated<E, A>): Validated<E, A>
Return this if it is Valid, or else fall back to the given default.
orNone common fun <E, A> Validated<E, A>.orNone(): Option<A>
orNull common fun <E, A> Validated<E, A>.orNull(): A?
Return the Valid value, or null if Invalid
redeem common inline fun <E, A, B> Validated<E, A>.redeem(fe: (E) -> B, fa: (A) -> B): Validated<E, B>
replicate common fun <E, A> Validated<E, A>.replicate(SE: Semigroup<E>, n: Int): Validated<E, List<A»
fun <E, A> Validated<E, A>.replicate(SE: Semigroup<E>, n: Int, MA: Monoid<A>): Validated<E, A>
sequence common fun <E, A> Validated<E, Iterable<A».sequence(): List<Validated<E, A»
sequenceEither common fun <E, A, B> Validated<A, Either<E, B».sequenceEither(): Either<E, Validated<A, B»
sequenceOption common fun <A, B> Validated<A, Option<B».sequenceOption(): Option<Validated<A, B»
toIor common fun <E, A> Validated<E, A>.toIor(): Ior<E, A>
Converts the value to an Ior<E, A>
valueOr common inline fun <E, A> Validated<E, A>.valueOr(f: (E) -> A): A
Return the Valid value, or the result of f if Invalid
widen common fun <E, B, A : B> Validated<E, A>.widen(): Validated<E, B>
Given A is a sub type of B, re-type this value from Validated<E, A> to Validated<E, B>kotlin:ank:playground:extension import arrow.core.*<br>fun main(args: Array<String>) { //sampleStart val string: Validated<Int, String> = "Hello".invalid() val chars: Validated<Int, CharSequence> = string.widen<Int, CharSequence, String>() //sampleEnd println(chars) }
zip common fun <E, A, B> Validated<E, A>.zip(SE: Semigroup<E>, fb: Validated<E, B>): Validated<E, Pair<A, B»
inline fun <E, A, B, Z> Validated<E, A>.zip(SE: Semigroup<E>, b: Validated<E, B>, f: (A, B) -> Z): Validated<E, Z>
inline fun <E, A, B, C, Z> Validated<E, A>.zip(SE: Semigroup<E>, b: Validated<E, B>, c: Validated<E, C>, f: (A, B, C) -> Z): Validated<E, Z>
inline fun <E, A, B, C, D, Z> Validated<E, A>.zip(SE: Semigroup<E>, b: Validated<E, B>, c: Validated<E, C>, d: Validated<E, D>, f: (A, B, C, D) -> Z): Validated<E, Z>
inline fun <E, A, B, C, D, EE, Z> Validated<E, A>.zip(SE: Semigroup<E>, b: Validated<E, B>, c: Validated<E, C>, d: Validated<E, D>, e: Validated<E, EE>, f: (A, B, C, D, EE) -> Z): Validated<E, Z>
inline fun <E, A, B, C, D, EE, FF, Z> Validated<E, A>.zip(SE: Semigroup<E>, b: Validated<E, B>, c: Validated<E, C>, d: Validated<E, D>, e: Validated<E, EE>, ff: Validated<E, FF>, f: (A, B, C, D, EE, FF) -> Z): Validated<E, Z>
inline fun <E, A, B, C, D, EE, F, G, Z> Validated<E, A>.zip(SE: Semigroup<E>, b: Validated<E, B>, c: Validated<E, C>, d: Validated<E, D>, e: Validated<E, EE>, ff: Validated<E, F>, g: Validated<E, G>, f: (A, B, C, D, EE, F, G) -> Z): Validated<E, Z>
inline fun <E, A, B, C, D, EE, F, G, H, Z> Validated<E, A>.zip(SE: Semigroup<E>, b: Validated<E, B>, c: Validated<E, C>, d: Validated<E, D>, e: Validated<E, EE>, ff: Validated<E, F>, g: Validated<E, G>, h: Validated<E, H>, f: (A, B, C, D, EE, F, G, H) -> Z): Validated<E, Z>
inline fun <E, A, B, C, D, EE, F, G, H, I, Z> Validated<E, A>.zip(SE: Semigroup<E>, b: Validated<E, B>, c: Validated<E, C>, d: Validated<E, D>, e: Validated<E, EE>, ff: Validated<E, F>, g: Validated<E, G>, h: Validated<E, H>, i: Validated<E, I>, f: (A, B, C, D, EE, F, G, H, I) -> Z): Validated<E, Z>
inline fun <E, A, B, C, D, EE, F, G, H, I, J, Z> Validated<E, A>.zip(SE: Semigroup<E>, b: Validated<E, B>, c: Validated<E, C>, d: Validated<E, D>, e: Validated<E, EE>, ff: Validated<E, F>, g: Validated<E, G>, h: Validated<E, H>, i: Validated<E, I>, j: Validated<E, J>, f: (A, B, C, D, EE, F, G, H, I, J) -> Z): Validated<E, Z>

Do you like Arrow?

Arrow Org
<