I’m looking to transform a `List<Validated<F...
# arrow
e
I’m looking to transform a
List<Validated<Foo, Unit>>
to a single
Validated<Bar, Unit>
. I’m having trouble figuring it out. This works:
Copy code
val failed = values().map {
    it.validate(vehicleSettingEntity, states) // a Validated<ValidationError, Unit>
}.filterIsInstance<Validated.Invalid<ValidationError>>().map { it.value }
return when {
    failed.isEmpty() -> Unit.valid()
    else -> ValidationResult(failed).invalid()
}
But it feels like some sort of
fold
might be better here. Can anyone provide a little guidance?
g
Maybe partition into
success/failure
?
Copy code
val (valid, failed) = values().partition { it.validate(vehicleSettingEntity, states) }

return when {
    failed.isEmpty() -> Unit.valid()
    else -> ValidationResult(failed).invalid()
}
e
that’s a little better, but
filter
is cleaner in that I don’t have to discard
valid
Copy code
val failed = values().filter {
    it.validate(vehicleSettingEntity, states)
}
return when {
    failed.isEmpty() -> Unit.valid()
    else -> ValidationResult(failed).invalid()
}
j
You could try
traverseValidated(Semigroup.list()) { it.map { listOf(it) } }.swap()
The idea being: We swap each Validated so that the thing we want to accumulate is in
Valid
and inside lists, then
traverseValidated
with the list semigroup will accumulate all `Valid`'s while using the semigroup from list (which just concatenates the lists) to produce one single result. Finally we have
Validated<Unit, List<Error>>
so we swap back to
Validated<List<Error>, Unit>
I typed this without testing but the general idea should work 🙂
Actually this is dumb
one sec
I forgot that Validated already accumulates the error side
traverseValidated(Semigroup.list()) { it.mapLeft(::listOf) }
should be all you need
e
that was really close:
Copy code
values().map {
            it.validate(vehicleSettingEntity, states)
        }.traverseValidated(Semigroup.list()) { it.mapLeft(::listOf) }
            .fold({ ValidationResult(it).invalid() }, { Unit.valid() })
thanks!
j
traverse***
methods are some of the most useful methods out there, but at times a little hard to approach. Especially now since it is no longer bundled in as the
Traversable
typeclass it makes it harder to understand imo. @simon.vergauwen we used to have some great docs about traverse, but I can't seem to find them anymore. Did they get lost when getting rid of typeclasses? If so we should probably rewrite some of it for the arrow-core docs...
e
something like
traverseAndFold
would be really nice sugar here. accumulate, then fold left/right
g
IMO the pure functional one:
Copy code
values().map {
  it.validate(vehicleSettingEntity, states)
}
.traverseValidated(Semigroup.list()) {
  it.mapLeft(::listOf)
}
.fold(
  { ValidationResult(it).invalid() },
  { Unit.valid() }
)
Is a step backwards from:
Copy code
val failed = values().filter {
    it.validate(vehicleSettingEntity, states)
}
return when {
    failed.isEmpty() -> Unit.valid()
    else -> ValidationResult(failed).invalid()
}
But it's all opinion at that point I think, ha
👍 2
j
It depends on what you are familiar with, I have done too much haskell to not directly take to
traverse
on these matters (also
traverse
extends to so much more than just this pattern and on so many more datatypes). But I can totally understand that one not used to this pattern can understand the filter one a lot easier, after all in kotlin, patterns like
Traversable
aren't usually as easy to use or understand, nor do they always provide more value. Btw both are pure functional,
traverse
is just more general/powerful, at the expense of being more cryptic.
👍 2
m
@Gavin Ray @Eric I’d personally recommend to still try traverse. It’s a matter of expressing the intent of the program. Traverse is a way better tool to express a bundled transformations on a collection that can either succeed / fail. We have a lot of more complex situations than this in our codebase. I can assure you, as soon as you need to deal with two or more nested layers of validated / eithers with filters and nested fold, the maintainability suffers tremendously. I generally recommend using
ValidateNel<E, A>
unless your
E
can be combined (i.e if you have a
Semigroup<E>
instance). This makes sense because when you think about it, when any of the validation fails, you’d expect at least one failure to have occurred. That’s exactly the contract of
NonEmptyList
because they guarantee that the first element always exists. Having said that, this is my general recommended pattern for the case. We have a LOT of these in our codebase, to a point that it becomes a standard pattern. That is: • given a list • traverseValidated each value • transform the invalid channel • transform the value
Copy code
val result: Validated<Bar, Unit> = 
  values() // our List<A>
    .traverseValidated { it.validate(...).toValidatedNel() } // ValidatedNel<E, List<A>> 
    .mapLeft { errs -> Bar(...) } // Validated<Bar, List<A>>
    .void() // Validated<Bar, Unit>
traverseValidated accumulates the error. If you want to quickly short circuit the computation on first failure, you can use traverseEither.
🔝 1
without comments, just code:
Copy code
val result: Validated<Bar, Unit> = 
  values() 
    .traverseValidated { it.validate(...).toValidatedNel() } 
    .mapLeft { errs -> Bar(...) }
    .void()
👍 1
g
Ah, using
.mapLeft()
here makes it a good deal easier to read, nice one!