3

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]]
Alexandr
  • 9,213
  • 12
  • 62
  • 102
  • 2
    It looks like you want to use [`Validated`](https://typelevel.org/cats/datatypes/validated.html) instead of `Either`. – Luis Miguel Mejía Suárez May 22 '19 at 13:54
  • @LuisMiguelMejíaSuárez, I've used it in cases when I know in advance the number of functions that return Either, I can combine them with Semigroupal.tupleN. But cannot get it how to use it in this context. Number of list elements is unknown. – Alexandr May 22 '19 at 13:59
  • Maybe something like `listWithEihers.foldMap(e => Validated.fromEither(e).toValidatedNec)`. – Luis Miguel Mejía Suárez May 22 '19 at 14:10
  • @LuisMiguelMejíaSuárez, foldMap, what is it? Cannot find it among method of List or Either – Alexandr May 22 '19 at 14:15
  • 1
    Oh sorry, it is an extension method provided by `import cats.syntax.foldable._` to any `C[_]` like type which have a instance of `Foldable[C]`. The signature is `foldMap[A, B](f: A => B)(implicit M: Monoid[B]): B` Basically it maps the values in the collection and fold them together using a monoid, and the monoid of `Validated` accumulates errors and values. – Luis Miguel Mejía Suárez May 22 '19 at 14:29
  • You don't want to fold it, you want to `traverse` or `sequence` with `Traverse`. – Andrey Tyukin May 22 '19 at 15:12

2 Answers2

7

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
Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
3

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.

Valy Dia
  • 2,781
  • 2
  • 12
  • 32