【问题标题】:How can I convert a list of Either to a list of Right values?如何将 Either 列表转换为 Right 值列表?
【发布时间】:2018-11-07 13:13:14
【问题描述】:

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

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

我将结果划分为:

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

在做一些错误处理后,我想返回一个Seq[T] 的有效项目。这意味着,返回所有 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.collect ... 在这里似乎是个不错的选择。
  • 你能避开中间的Right吗? collect 似乎很麻烦,get 似乎很危险/不必要。
  • 您可能有兴趣使用 Cats Validated 来累积错误或结果 (typelevel.org/cats/datatypes/validated.html) - 您可以避免使用 Either 所需的样板,例如分区和不必要的 .get 调用。
  • items.flatMap(_.toOption) 在我的测试中运行良好,是一个干净易懂的解决方案。

标签: scala either


【解决方案1】:

Scala 2.13 开始,您可能更喜欢partitionMap 而不是partition

它根据返回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)

它让您可以独立地使用 left 和 right 并使用正确的类型。

【讨论】:

    【解决方案2】:

    使用.get 被认为是“代码异味”:在这种情况下它会起作用,但会使代码的阅读者暂停并花费一些额外的“循环”以“证明”它是可以的。最好避免在EitherOption 上使用.get 或在MapIndexedSeq 上使用.apply

    .getOrElse 没问题...但是null 不是您在 scala 代码中经常看到的东西。再一次,让读者停下来思考“为什么会在这里?如果它最终返回 null 会发生什么?”等等。最好也避免。

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

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

    您也可以使用foldRight 进行分区和提取一次“进行”:

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

    【讨论】:

    • 应该是case (Right(result), (e, i)) => (e, result :: i)
    【解决方案3】:

    一一浏览:

    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}
    

    这是另一种情况“它有效,但为什么这么复杂?”。在存在 Left 项目的情况下,这也不是详尽无遗的,但这绝不应该发生,因此无需使用 .collect

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

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

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

    如果您要像这样对results 进行分区,我推荐第一个选项items.map(_.right.get)。任何其他选项要么具有无法访问的代码(您永远无法通过单元测试或实际操作的代码),要么为了“看起来功能”而不必要地复杂。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-17
      • 2017-01-21
      • 2016-05-04
      • 1970-01-01
      • 1970-01-01
      • 2021-10-10
      相关资源
      最近更新 更多