arrow-core-data / arrow.core / Validated
sealed class Validated<out E, out A> :
ValidatedOf
<E, 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.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.
Validated
.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.a, v2.a))
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.a, v2.a))
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.e, listOf(v2.e)))
else -> throw IllegalStateException("Not possible value")
}
//sampleEnd
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
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")
}
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)
}
Invalid | data class Invalid<out E> : Validated <E, Nothing > |
Valid | data class Valid<out A> : Validated < Nothing , A> |
isInvalid | val isInvalid: Boolean |
isValid | val isValid: Boolean |
all | fun all(predicate: (A) -> Boolean ): Boolean |
bifoldLeft | fun <B> bifoldLeft(c: B, fe: (B, E) -> B, fa: (B, A) -> B): B |
bifoldMap | fun <B> bifoldMap(MN: Monoid <B>, g: (E) -> B, f: (A) -> B): B |
bifoldRight | fun <B> ~~bifoldRight~~(c: Eval <B>, fe: (E, Eval <B>) -> Eval <B>, fa: (A, Eval <B>) -> Eval <B>): Eval <B> |
bimap | From arrow.typeclasses.Bifunctor, maps both types of this Validated.fun <EE, B> bimap(fe: (E) -> EE, fa: (A) -> B): Validated <EE, B> |
bitraverse | fun <EE, B> bitraverse(fe: (E) -> Iterable <EE>, fa: (A) -> Iterable <B>): List < Validated <EE, B>> |
bitraverseEither | fun <EE, B, C> bitraverseEither(fe: (E) -> Either <EE, B>, fa: (A) -> Either <EE, C>): Either <EE, Validated <B, C>> |
exist | Is this Valid and matching the given predicatefun exist(predicate: (A) -> Boolean ): Boolean |
findOrNull | fun findOrNull(predicate: (A) -> Boolean ): A? |
fold | fun <B> fold(fe: (E) -> B, fa: (A) -> B): B |
foldLeft | apply the given function to the value with the given B when valid, otherwise return the given Bfun <B> foldLeft(b: B, f: (B, A) -> B): B |
foldMap | fun <B> foldMap(MB: Monoid <B>, f: (A) -> B): B |
foldRight | fun <B> ~~foldRight~~(lb: Eval <B>, f: (A, Eval <B>) -> Eval <B>): Eval <B> |
isEmpty | fun isEmpty(): Boolean |
isNotEmpty | fun isNotEmpty(): Boolean |
leftMap | fun <EE> ~~leftMap~~(f: (E) -> EE): Validated <EE, A> |
map | Apply a function to a Valid value, returning a new Valid valuefun <B> map(f: (A) -> B): Validated <E, B> |
mapLeft | Apply a function to an Invalid value, returning a new Invalid value. Or, if the original valid was Valid, return it.fun <EE> mapLeft(f: (E) -> EE): Validated <EE, A> |
show | fun ~~show~~(SE: Show <E>, SA: Show <A>): String |
swap | fun swap(): Validated <A, E> |
toEither | Converts the value to an Either<E, A>fun toEither(): Either <E, A> |
toList | Convert this value to a single element List if it is Valid, otherwise return an empty Listfun toList(): List <A> |
toOption | Returns Valid values wrapped in Some, and None for Invalid valuesfun toOption(): Option <A> |
toString | open fun toString(): String |
toValidatedNel | Lift the Invalid value into a NonEmptyList.fun toValidatedNel(): ValidatedNel <E, A> |
traverse | fun <B> traverse(fa: (A) -> Iterable <B>): List < Validated <E, B>> |
traverseEither | fun <EE, B> traverseEither(fa: (A) -> Either <EE, B>): Either <EE, Validated <E, B>> |
void | Discards the A value inside Validated signaling this container may be pointing to a noop or an effect whose return value is deliberately ignored. The singleton value Unit serves as signal.fun void(): Validated <E, Unit > |
withEither | 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 <EE, B> withEither(f: ( Either <E, A>) -> Either <EE, B>): Validated <EE, B> |
catch | fun <A> catch(f: () -> A): Validated < Throwable , A> suspend fun <A> ~~catch~~(f: suspend () -> A): Validated < Throwable , A> fun <E, A> catch(recover: ( Throwable ) -> E, f: () -> A): Validated <E, A> |
catchNel | fun <A> catchNel(f: () -> A): ValidatedNel < Throwable , A> suspend fun <A> ~~catchNel~~(f: suspend () -> A): ValidatedNel < Throwable , A> |
fromEither | Converts an Either<E, A> to a Validated<E, A> .fun <E, A> fromEither(e: Either <E, A>): Validated <E, A> |
fromNullable | Converts a nullable A? to a Validated<E, A> , where the provided ifNull output value is returned as Invalid when the specified value is null.fun <E, A> fromNullable(value: A?, ifNull: () -> E): Validated <E, A> |
fromOption | Converts an Option<A> to a Validated<E, A> , where the provided ifNone output value is returned as Invalid when the specified Option is None .fun <E, A> fromOption(o: Option <A>, ifNone: () -> E): Validated <E, A> |
invalidNel | fun <E, A> invalidNel(e: E): ValidatedNel <E, A> |
lift | Lifts a function A -> B to the Validated structure.fun <E, A, B> lift(f: (A) -> B): ( Validated <E, A>) -> Validated <E, B> Lifts two functions to the Bifunctor type. fun <A, B, C, D> lift(fl: (A) -> C, fr: (B) -> D): ( Validated <A, B>) -> Validated <C, D> |
validNel | fun <E, A> validNel(a: A): ValidatedNel <E, A> |
altFold | fun <T, F, A> Kind<T, A>.altFold(AF: Alternative <F>, FT: Foldable <T>): Kind<F, A> |
altSum | fun <T, F, A> Kind<T, Kind<F, A>>.altSum(AF: Alternative <F>, FT: Foldable <T>): Kind<F, A> |
ap | fun <E, A, B> ValidatedOf <E, A>.~~ap~~(SE: Semigroup <E>, f: Validated <E, (A) -> B>): Validated <E, B> |
attempt | fun <E, A> Validated <E, A>.attempt(): Validated < Nothing , Either <E, A>> |
bisequence | fun <E, A> Validated < Iterable <E>, Iterable <A>>.bisequence(): List < Validated <E, A>> |
bisequenceEither | fun <E, A, B> Validated < Either <E, A>, Either <E, B>>.bisequenceEither(): Either <E, Validated <A, B>> |
combine | fun <E, A> ValidatedOf <E, A>.combine(SE: Semigroup <E>, SA: Semigroup <A>, y: ValidatedOf <E, A>): Validated <E, A> |
combineAll | fun <E, A> Validated <E, A>.combineAll(MA: Monoid <A>): A |
combineK | fun <E, A> ValidatedOf <E, A>.combineK(SE: Semigroup <E>, y: ValidatedOf <E, A>): Validated <E, A> |
compareTo | operator fun <E : Comparable <E>, A : Comparable <A>> Validated <E, A>.compareTo(other: Validated <E, A>): Int |
conest | fun <F, A, B> CounnestedType <F, A, B>.conest(): ConestedType <F, A, B> |
findValid | 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 <E, A> ValidatedOf <E, A>.findValid(SE: Semigroup <E>, that: () -> Validated <E, A>): Validated <E, A> |
fix | fun <E, A> ValidatedOf <E, A>.~~fix~~(): Validated <E, A> |
fold | fun <E, A> Validated <E, A>.fold(MA: Monoid <A>): A |
getOrElse | Return the Valid value, or the default if Invalidfun <E, A> ValidatedOf <E, A>.getOrElse(default: () -> A): A |
handleError | fun <E, A> ValidatedOf <E, A>.handleError(f: (E) -> A): Validated < Nothing , A> |
handleErrorWith | fun <E, A> ValidatedOf <E, A>.handleErrorWith(f: (E) -> ValidatedOf <E, A>): Validated <E, A> |
handleLeftWith | fun <E, A> ValidatedOf <E, A>.~~handleLeftWith~~(f: (E) -> ValidatedOf <E, A>): Validated <E, A> |
leftWiden | fun <EE, E : EE, A> Validated <E, A>.leftWiden(): Validated <EE, A> |
orElse | 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 <E, A> ValidatedOf <E, A>.orElse(default: () -> Validated <E, A>): Validated <E, A> |
orNull | Return the Valid value, or null if Invalidfun <E, A> ValidatedOf <E, A>.orNull(): A? |
redeem | fun <E, A, B> Validated <E, A>.redeem(fe: (E) -> B, fa: (A) -> B): Validated <E, B> |
replicate | 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 | fun <E, A> Validated <E, Iterable <A>>.sequence(): List < Validated <E, A>> fun <G, E, A> ValidatedOf <E, Kind<G, A>>.~~sequence~~(GA: Applicative <G>): Kind<G, Validated <E, A>> |
sequenceEither | fun <E, A, B> Validated <A, Either <E, B>>.sequenceEither(): Either <E, Validated <A, B>> |
toIor | Converts the value to an Ior<E, A>fun <E, A> ValidatedOf <E, A>.toIor(): Ior <E, A> |
traverse | fun <G, E, A, B> ValidatedOf <E, A>.~~traverse~~(GA: Applicative <G>, f: (A) -> Kind<G, B>): Kind<G, Validated <E, B>> |
valueOr | Return the Valid value, or the result of f if Invalidfun <E, A> ValidatedOf <E, A>.valueOr(f: (E) -> A): A |
widen | Given A is a sub type of B, re-type this value from Validated<E, A> to Validated<E, B>fun <E, B, A : B> Validated <E, A>.widen(): Validated <E, B> |
zip | fun <E, A, B> Validated <E, A>.zip(SE: Semigroup <E>, fb: Validated <E, B>): Validated <E, Pair <A, B>> fun <E, A, B, Z> Validated <E, A>.zip(SE: Semigroup <E>, b: Validated <E, B>, f: (A, B) -> Z): Validated <E, Z> 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> 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> 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> 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> 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> 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> 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> 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> fun <E, A, B, Z> ValidatedNel <E, A>.zip(b: ValidatedNel <E, B>, f: (A, B) -> Z): ValidatedNel <E, Z> fun <E, A, B, C, Z> ValidatedNel <E, A>.zip(b: ValidatedNel <E, B>, c: ValidatedNel <E, C>, f: (A, B, C) -> Z): ValidatedNel <E, Z> fun <E, A, B, C, D, Z> ValidatedNel <E, A>.zip(b: ValidatedNel <E, B>, c: ValidatedNel <E, C>, d: ValidatedNel <E, D>, f: (A, B, C, D) -> Z): ValidatedNel <E, Z> fun <E, A, B, C, D, EE, Z> ValidatedNel <E, A>.zip(b: ValidatedNel <E, B>, c: ValidatedNel <E, C>, d: ValidatedNel <E, D>, e: ValidatedNel <E, EE>, f: (A, B, C, D, EE) -> Z): ValidatedNel <E, Z> fun <E, A, B, C, D, EE, FF, Z> ValidatedNel <E, A>.zip(b: ValidatedNel <E, B>, c: ValidatedNel <E, C>, d: ValidatedNel <E, D>, e: ValidatedNel <E, EE>, ff: ValidatedNel <E, FF>, f: (A, B, C, D, EE, FF) -> Z): ValidatedNel <E, Z> fun <E, A, B, C, D, EE, F, G, Z> ValidatedNel <E, A>.zip(b: ValidatedNel <E, B>, c: ValidatedNel <E, C>, d: ValidatedNel <E, D>, e: ValidatedNel <E, EE>, ff: ValidatedNel <E, F>, g: ValidatedNel <E, G>, f: (A, B, C, D, EE, F, G) -> Z): ValidatedNel <E, Z> fun <E, A, B, C, D, EE, F, G, H, Z> ValidatedNel <E, A>.zip(b: ValidatedNel <E, B>, c: ValidatedNel <E, C>, d: ValidatedNel <E, D>, e: ValidatedNel <E, EE>, ff: ValidatedNel <E, F>, g: ValidatedNel <E, G>, h: ValidatedNel <E, H>, f: (A, B, C, D, EE, F, G, H) -> Z): ValidatedNel <E, Z> fun <E, A, B, C, D, EE, F, G, H, I, Z> ValidatedNel <E, A>.zip(b: ValidatedNel <E, B>, c: ValidatedNel <E, C>, d: ValidatedNel <E, D>, e: ValidatedNel <E, EE>, ff: ValidatedNel <E, F>, g: ValidatedNel <E, G>, h: ValidatedNel <E, H>, i: ValidatedNel <E, I>, f: (A, B, C, D, EE, F, G, H, I) -> Z): ValidatedNel <E, Z> fun <E, A, B, C, D, EE, F, G, H, I, J, Z> ValidatedNel <E, A>.zip(b: ValidatedNel <E, B>, c: ValidatedNel <E, C>, d: ValidatedNel <E, D>, e: ValidatedNel <E, EE>, ff: ValidatedNel <E, F>, g: ValidatedNel <E, G>, h: ValidatedNel <E, H>, i: ValidatedNel <E, I>, j: ValidatedNel <E, J>, f: (A, B, C, D, EE, F, G, H, I, J) -> Z): ValidatedNel <E, Z> |
Do you like Arrow?
✖