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

匆匆过客 提交于 2019-12-10 18:47:32

问题


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



回答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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!