Search code examples
kotlinkotlin-coroutinesarrow-kt

Kotlin arrow combine list of validated


I have the following classes:

class Person(id: Long, name: String)

sealed class PersonError {
    data class InvalidId(val field: String) : PersonError()
    data class InvalidName(val field: String) : PersonError()
}

As I loop and validate through multiple persons, I get:

List<ValidatedNel<Error, Person>>

or:

List<Validated<Error, Person>>

How do I transform the above list to:

Validated<Nel<MappingError>, List<Person>>

I want to accumulate all errors related to the validation of all persons. This is needed, because I want to do the following:

val vId : ValidatedNel<Error, Long> = validateId(id).toValidatedNel()
val vPersons : List<ValidatedNel<Error, Person>> = validatePersons(persons).toValidatedNel()

ValidatedNel.applicative<Nel<PersonError>>(Nel.semigroup<PersonError>())
    .map(vId, vPersons) {
        val id = it.a
        val persons = it.b
        Group(id, persons)
    }.fix()

The current 'map' does not accept a List<ValidatedNel<Error>, Person>>


Solution

  • The left side of Validated requires a Semigroup to collect all errors; the right side of your Validated requires a product (since Group(a, b) is a product type); and the accumulation of errors within a specific person requires a sequence (or traverse with identity function) to collect all errors for a given person, so the following should work:

            val SE = Nel.semigroup<PersonError>()
    
            val validatedGroup: ValidatedNel<PersonError, Group> = vId.product(
                    SE,
                    vPersons.sequence(ValidatedNel.applicative(SE)).fix().map { it.fix() }
            ).map(::Group.tupled2())
    

    I think in an upcoming release of Arrow the calls to fix() will be minimized or eliminated, so hopefully .fix().map { it.fix() } can be removed soon.