Search code examples
scalavalidationerror-handlingscala-catseither

Scala: Combine Either per the whole List with Either per elements


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]]

Solution

  • 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.fromEither maps Either[List[String], X] to Validated[List[String], X], that's exactly what you need as function in traverse.

    Therefore, you might try:

    • l.traverse(Validated.fromEither) if you are OK with a Validated
    • l.traverse(Validated.fromEither).toEither if you really want an Either in 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