问题
I have a list of Either, which represents error:
type ErrorType = List[String]
type FailFast[A] = Either[ErrorType, A]
import cats.syntax.either._
val l = List(1.asRight[ErrorType], 5.asRight[ErrorType])
If all of them are right, I want to get a list of [A], in this case - List[Int]
If any Either is left, I want to combine all errors of all either and return it.
I've found a similar topic at [How to reduce a Seq[Either[A,B]] to a Either[A,Seq[B]]
But it was quite long ago. For instance, one of the answers offers to use partitionMap, which I cannot find at this moment. Probably there is a better, more elegant solution. Example with scala-cats would be great.
How I would like to use it:
for {
listWithEihers <- someFunction
//if this list contains one or more errors, return Left[List[String]]
//if everything is fine, convert it to:
correctItems <- //returns list of List[Int] as right
} yield correctItems
Return type of this for-comprehension must be:
Either[List[String], List[Int]]
回答1:
As already mentioned in the comments, Either is good for fail-fast behavior. For accumulating multiple errors, you probably want something like Validated. Moreover:
- List is traversable (has instance of
Traverse) - Validated is applicative
Validated.fromEithermapsEither[List[String], X]toValidated[List[String], X], that's exactly what you need as function intraverse.
Therefore, you might try:
l.traverse(Validated.fromEither)if you are OK with aValidatedl.traverse(Validated.fromEither).toEitherif you really want anEitherin the end.
Full example with all imports:
import cats.data.Validated
import cats.syntax.validated._
import cats.syntax.either._
import cats.syntax.traverse._
import cats.instances.list._
import cats.Traverse
import scala.util.Either
type ErrorType = List[String]
type FailFast[A] = Either[ErrorType, A]
val l: List[Either[ErrorType, Int]] = List(1.asRight[ErrorType], 5.asRight[ErrorType])
// solution if you want to keep a `Validated`
val validatedList: Validated[ErrorType, List[Int]] =
l.traverse(Validated.fromEither)
// solution if you want to transform it back to `Either`
val eitherList: Either[ErrorType, List[Int]] =
l.traverse(Validated.fromEither).toEither
回答2:
As @Luis mention in the comments, ValidatedNel is what you are looking for:
import cats.data.{ Validated, ValidatedNel }
import cats.implicits._
type ErrorType = String
def combine(listWithEither: List[Either[ErrorType, Int]]):ValidatedNel[ErrorType, List[Int]] =
listWithEither.foldMap(e => Validated.fromEither(e).map(List(_)).toValidatedNel)
val l1 = List[Either[ErrorType, Int]](Right(1), Right(2), Right(3))
val l2 = List[Either[ErrorType, Int]](Left("Incorrect String"), Right(2), Left("Validation error"))
println(combine(l1))
// Displays Valid(List(1, 2, 3))
println(combine(l2))
// Displays Invalid(NonEmptyList(Incorrect String, Validation error))
You could transform the final rather back to an Either using .toEither, but ValidatedNel is a better structure to accumulate errors, while Either is more suited for fail fast erroring.
来源:https://stackoverflow.com/questions/56258434/scala-combine-either-per-the-whole-list-with-either-per-elements