如何将 Either 列表转换为 Right 值列表?

How can I convert a list of Either to a list of Right values?

我对字符串列表进行了一些数据转换,得到了一个 Either 列表,其中 Left 表示错误,Right 表示成功转换的项目。

val results: Seq[Either[String, T]] = ... 

我将我的结果划分为:

val (errors, items) = results.partition(_.isLeft)

在进行一些错误处理后,我想 return Seq[T] 个有效项目。这意味着,returning 所有 Right 元素的值。由于分区,我已经知道项目的所有元素 Right。我想出了五种可能的方法。但是什么是可读性和性能最好的呢?在 Scala 中有一种惯用的方法吗?

// which variant is most scala like and still understandable?
items.map(_.right.get)
items.map(_.right.getOrElse(null))
items.map(_.asInstanceOf[Right[String, T]].value)
items.flatMap(_.toOption)
items.collect{case Right(item) => item}

一一浏览:

items.map(_.right.get)

你已经知道这些都是权利。这绝对没问题。

items.map(_.right.getOrElse(null))

此处不需要 .getOrElse,因为您已经知道它永远不会发生。如果你发现一个 Left(不知何故),我建议抛出一个异常,像这样:items.map(x => x.right.getOrElse(throw new Exception(s"Unexpected Left: [$x]"))(或你认为合适的任何异常),而不是干预 null 值。

items.map(_.asInstanceOf[Right[String, T]].value)

这太复杂了。我也无法编译它,但我可能做错了什么。无论哪种方式,都不需要在此处使用 asInstanceOf

items.flatMap(_.toOption)

我也无法编译它。 items.flatMap(_.right.toOption) 为我编译,但到那时它总是一个 Some,你仍然需要 .get 它。

items.collect{case Right(item) => item}

这是"it works but why be so complicated?"的另一个例子。在存在 Left 项的情况下,它也不是详尽无遗的,但这永远不会发生,因此无需使用 .collect.

获得正确值的另一种方法是使用模式匹配:

items.map {
  case Right(value) => value
  case other => throw new Exception(s"Unexpected Left: $other")
}

但同样,这可能是不必要的,因为您已经知道所有值都是正确的。

如果你要像这样分割results,我推荐第一个选项,items.map(_.right.get)。任何其他选项要么具有无法访问的代码(您永远无法通过单元测试或实际操作访问的代码),要么为了 "looking functional".

而不必要地复杂化

考虑使用.get "code smell":在这种情况下它会起作用,但会使代码的reader暂停并花费一些额外的"cycles" to "prove" 没关系。最好避免在 Either 上使用 .get,在 MapIndexedSeq 上使用 Option.apply

.getOrElse 没问题......但是 null 不是你在 scala 代码中经常看到的东西。同样,让 reader 停下来思考 "why is this here? what will happen if it ends up returning null?" 等。最好也避免。

.asInstanceOf 是……很糟糕。它破坏了类型安全,只是……不是 scala。

剩下 .flatMap(_.toOption).collect。两者都很好。我个人更喜欢后者,因为它更明确一点(并且不会让 reader 停下来记住 Either 是有偏见的)。

您也可以使用 foldRight 将分区和提取合二为一 "go":

 val (errors, items) = results.foldRight[(List[String], List[T])](Nil,Nil) { 
    case (Left(error), (e, i)) => (error :: e, i)
    case ((Right(result), (e, i)) => (e, result :: i)
 }

Scala 2.13 开始,您可能更喜欢 partitionMap to partition

它根据 returns RightLeft 的函数对元素进行分区。在您的情况下,这只是 identity:

val (lefts, rights) = List(Right(1), Left("2"), Left("3")).partitionMap(identity)
// val lefts:  List[String] = List(2, 3)
// val rights: List[Int]    = List(1)

这让您可以独立使用左手和右手并使用正确的类型。