Scala:将整个列表中的任何一个与每个元素中的任何一个结合起来

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

我有一个 Either 列表,表示错误:

type ErrorType = List[String]
type FailFast[A] = Either[ErrorType, A]

import cats.syntax.either._
val l = List(1.asRight[ErrorType], 5.asRight[ErrorType])

如果全部正确,我想获得 [A] 的列表,在本例中 - List[Int]

如果还有 Either,我想将所有错误和 return 合并。

我在 [How to reduce a Seq[Either[A,B]] to a Either[A,Seq[B]]

找到了类似的主题

但那是很久以前的事了。例如,其中一个答案提供使用 partitionMap,我现在找不到。可能有更好、更优雅的解决方案。 scala-cats 的例子会很棒。

我想如何使用它:

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这个求理解的类型必须是:

Either[List[String], List[Int]]

正如@Luis 在评论中提到的那样,ValidatedNel 就是您要查找的内容:

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

您可以使用 .toEither 将最终的而不是转换回 Either,但是 ValidatedNel 是一个更好的累积错误的结构,而 Either 更适合fail fast 错误。

正如评论中已经提到的,Either 有利于 fail-fast 行为。对于累积多个错误,您可能需要 Validated 之类的东西。此外:

  • 列表是可遍历的(具有 Traverse 的实例)
  • 已验证适用
  • Validated.fromEitherEither[List[String], X] 映射到 Validated[List[String], X],这正是您在 traverse.
  • 中需要的功能

因此,您可以尝试:

  • l.traverse(Validated.fromEither) 如果你觉得 Validated
  • l.traverse(Validated.fromEither).toEither 如果你真的想要一个 Either 最后。

包含所有导入的完整示例:

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