Eric
09/29/2021, 5:15 PMList<Validated<Foo, Unit>>
to a single Validated<Bar, Unit>
. I’m having trouble figuring it out. This works:
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?Gavin Ray
09/29/2021, 6:34 PMsuccess/failure
?Gavin Ray
09/29/2021, 6:35 PMval (valid, failed) = values().partition { it.validate(vehicleSettingEntity, states) }
return when {
failed.isEmpty() -> Unit.valid()
else -> ValidationResult(failed).invalid()
}
Eric
09/29/2021, 6:42 PMfilter
is cleaner in that I don’t have to discard valid
val failed = values().filter {
it.validate(vehicleSettingEntity, states)
}
return when {
failed.isEmpty() -> Unit.valid()
else -> ValidationResult(failed).invalid()
}
Jannis
09/29/2021, 6:44 PMtraverseValidated(Semigroup.list()) { it.map { listOf(it) } }.swap()
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>
Jannis
09/29/2021, 6:45 PMJannis
09/29/2021, 6:45 PMJannis
09/29/2021, 6:46 PMJannis
09/29/2021, 6:47 PMtraverseValidated(Semigroup.list()) { it.mapLeft(::listOf) }
should be all you needEric
09/29/2021, 6:55 PMvalues().map {
it.validate(vehicleSettingEntity, states)
}.traverseValidated(Semigroup.list()) { it.mapLeft(::listOf) }
.fold({ ValidationResult(it).invalid() }, { Unit.valid() })
thanks!Jannis
09/29/2021, 7:01 PMtraverse***
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...Eric
09/29/2021, 7:03 PMtraverseAndFold
would be really nice sugar here. accumulate, then fold left/rightGavin Ray
09/29/2021, 7:46 PMvalues().map {
it.validate(vehicleSettingEntity, states)
}
.traverseValidated(Semigroup.list()) {
it.mapLeft(::listOf)
}
.fold(
{ ValidationResult(it).invalid() },
{ Unit.valid() }
)
Is a step backwards from:
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, haJannis
09/29/2021, 7:55 PMtraverse
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.mitch
09/30/2021, 11:11 AMValidateNel<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
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.mitch
09/30/2021, 11:13 AMval result: Validated<Bar, Unit> =
values()
.traverseValidated { it.validate(...).toValidatedNel() }
.mapLeft { errs -> Bar(...) }
.void()
Gavin Ray
10/02/2021, 4:52 PM.mapLeft()
here makes it a good deal easier to read, nice one!