【问题标题】:Reducing Iterable[Either[A,B]] to Either[A, Iterable[B]]将 Iterable[Either[A,B]] 减少为 Either[A, Iterable[B]]
【发布时间】:2012-10-18 06:12:18
【问题描述】:

我需要将 Iterable[Either[Throwable, String]] 简化为 Either[Throwable, Iterable[String]]。我不知道这个操作是否很常见,在 Iterable 特征上没有发现任何东西。所以我写了这个函数:

def reduce[A, B](xs: Iterable[Either[A, B]]): Either[A, Iterable[B]] = 
  xs.collectFirst {
    case Left(x) => x
  } match {
    case Some(x) => Left(x)
    case None => Right(xs.collect{case Right(y)=> y})
  }

如果不是这个,谁能帮我找到更好的方法?

【问题讨论】:

  • 你想要实现的转换有点模棱两可。例如,您的输入列表包含一半的Right[String]s 和一半的各种和异构的Left[Exception]s。您想将其减少为一个异常或字符串列表。如果有例如应该采取哪个例外输入有十个不同?
  • 你是对的。我只想考虑第一个异常(或任何 Left 值),它将隐藏其他异常,但我的用例可以接受。

标签: scala functional-programming iterable either


【解决方案1】:

我总是觉得return 语句有点尴尬,但下面的方法有效:

def reduce[A, B](xs: Iterable[Either[A, B]]): Either[A, Iterable[B]] =
  Right(xs.collect {
    case Left(x) => return Left(x)
    case Right(x) => x
  })

【讨论】:

  • case Left(x) => return Left(x) 可以简写为case l@Left(_) => return l
  • @KimStebel 是的,一开始我是这么想的,但是结果EitherB 类型参数是错误的(它需要Iterable[B],但是是B),所以@987654329 @ 是不同的Left
  • 谢谢,但我不喜欢使用 return 语句。
  • 我认为这是使用return 的一个很好的例子(不喜欢return 不应该成为教条)。迭代地处理某些事情,但提前保释并返回一些特殊情况。这就像一个收集 collectFirst(尽管名称并不真正收集),它确实在其实现中使用了 return
【解决方案2】:

如果您不喜欢显式返回并希望在稍微缩短代码的同时消除模式匹配,这里是另一个版本:

def reduce[A, B](xs: Iterable[Either[A, B]]): Either[A, Iterable[B]] =
  xs collectFirst {
    case Left(x) => Left(x)
  } getOrElse Right(xs.flatMap(_.right.toOption))

【讨论】:

    【解决方案3】:

    此操作通常称为排序,在某些函数式语言(如 Haskell)的标准库中可用。在 Scala 中,您可以实现自己的,也可以使用外部库,如 Scalaz。假设我们有以下内容,例如:

    val xs: List[Either[String, Int]] = List(Right(1), Right(2))
    val ys: List[Either[String, Int]] = List(Right(1), Left("1st!"), Left("2nd!"))
    

    现在我们可以编写(使用 Scalaz 7):

    scala> import scalaz._, Scalaz._
    import scalaz._
    import Scalaz._
    
    scala> xs.sequenceU
    res0: Either[String,List[Int]] = Right(List(1, 2))
    
    scala> ys.sequenceU
    res1: Either[String,List[Int]] = Left(1st!)
    

    根据需要。


    附带说明,此操作只要求外部容器是可遍历的,并且内部容器是应用函子。 Scalaz 还提供了一个ValidationNEL 类,它很像Either 并且也符合这些要求,但是在ValidationNELs 列表上使用sequence 会收集多个错误,而不是在第一个停止:

    val zs: List[ValidationNEL[String, Int]] =
      List(1.successNel, "1st".failNel, "2nd".failNel)
    

    现在我们得到:

    scala> print(zs.sequenceU)
    Failure(NonEmptyList(1st, 2nd))
    

    您还可以在Options、Promises 等列表中使用sequence

    【讨论】:

    • 其实和Akka框架中的Future.sequence很像吧?
    • @FilippoDeLuca:是的,确切地说,它没有 Scalaz 的通用。
    • 我还不能完全理解scalaz,但我确实必须尝试一下,或者更好,它必须给我一个尝试:)
    • @FilippoDeLuca:我自己绝对不完全理解 Scalaz!它的好处之一是您不需要 — 只需选择一些起点并四处走动。
    猜你喜欢
    • 2011-11-06
    • 2019-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-08
    • 2016-10-27
    • 2021-11-10
    • 1970-01-01
    相关资源
    最近更新 更多