【问题标题】:Flatten Scala Try展平 Scala 尝试
【发布时间】:2013-03-07 21:39:26
【问题描述】:

有没有一种简单的方法来展平一组 try 以给出 try 值的成功或失败? 例如:

def map(l:List[Int]) = l map {
  case 4 => Failure(new Exception("failed"))
  case i => Success(i)
}

val l1 = List(1,2,3,4,5,6)
val result1 = something(map(l1))

result1: Failure(Exception("failed"))

val l2 = List(1,2,3,5,6)
val result2 = something(map(l2)) 

result2: Try(List(1,2,3,5,6))

您将如何处理集合中的多个失败?

【问题讨论】:

  • 请说出您的结果应该是什么样子。 List(1, 2, 3, Exception, 5, 7)?
  • Scalaz 会将此操作称为 sequence,但遗憾的是标准库中缺少它。

标签: scala try-catch scala-2.10


【解决方案1】:

也许不像你希望的那么简单,但这很有效:

def flatten[T](xs: Seq[Try[T]]): Try[Seq[T]] = {
  val (ss: Seq[Success[T]]@unchecked, fs: Seq[Failure[T]]@unchecked) =
    xs.partition(_.isSuccess)

  if (fs.isEmpty) Success(ss map (_.get))
  else Failure[Seq[T]](fs(0).exception) // Only keep the first failure
}

val xs = List(1,2,3,4,5,6)
val ys = List(1,2,3,5,6)

println(flatten(map(xs))) // Failure(java.lang.Exception: failed)
println(flatten(map(ys))) // Success(List(1, 2, 3, 5, 6))

请注意,partition 的使用并不像 @unchecked 注释所证明的那样类型安全。在这方面,累积两个序列Seq[Success[T]]Seq[Failure[T]]foldLeft 会更好。

如果你想保留所有的失败,你可以使用这个:

def flatten2[T](xs: Seq[Try[T]]): Either[Seq[T], Seq[Throwable]] = {
  val (ss: Seq[Success[T]]@unchecked, fs: Seq[Failure[T]]@unchecked) =
    xs.partition(_.isSuccess)

  if (fs.isEmpty) Left(ss map (_.get))
  else Right(fs map (_.exception))
}

val zs = List(1,4,2,3,4,5,6)

println(flatten2(map(xs))) // Right(List(java.lang.Exception: failed))
println(flatten2(map(ys))) // Left(List(1, 2, 3, 5, 6))
println(flatten2(map(zs))) // Right(List(java.lang.Exception: failed, 
                           //            java.lang.Exception: failed))

【讨论】:

  • +1 " 在这方面,累积两个序列 Seq[Success[T]] 和 Seq[Failure[T]] 的 foldLeft 会更好。" 是的! scala 生态系统中有很多验证或失败类型(Option、Either、Try、scalaz.Disjunction、scalaz.Validation、...),但我仍然无法在公共方法和常见的模式。
【解决方案2】:

不那么冗长,更安全:

def sequence[T](xs : Seq[Try[T]]) : Try[Seq[T]] = (Try(Seq[T]()) /: xs) {
    (a, b) => a flatMap (c => b map (d => c :+ d))
}

结果:

sequence(l1)

res8: scala.util.Try[Seq[Int]] = Failure(java.lang.Exception: failed)

sequence(l2)

res9: scala.util.Try[Seq[Int]] = Success(List(1, 2, 3, 5, 6))

【讨论】:

  • 看起来不错,但追加操作不会对性能造成很大影响吗?
  • 公平点 - 我忘记了默认的 SeqList。自行决定添加反向/使用向量!
  • 很好,在/:之前我没有看到 foldLeft 快捷方式
【解决方案3】:

看看 liftweb Box monad。在tryo 构造函数的帮助下,它为您提供了您正在寻找的抽象。

使用tryo,您可以将函数提升为Box。然后,该框要么包含函数的结果,要么包含错误。然后,您可以使用通常的一元辅助函数(flatMap、filter 等)访问该框,而无需担心该框是否包含错误或函数的结果。

例子:

import net.liftweb.util.Helpers.tryo

List("1", "2", "not_a_number") map (x => tryo(x.toInt)) map (_ map (_ + 1 ))

结果到

List[net.liftweb.common.Box[Int]] = 
  List(
    Full(2), 
    Full(3), 
    Failure(For input string: "not_a_number",Full(java.lang.NumberFormatException: For input string: "not_a_number"),Empty)
  )

您可以使用flatMap 跳过错误值

List("1", "2", "not_a_number") map (x => tryo(x.toInt)) flatMap (_ map (_ + 1 ))

结果

List[Int] = List(2, 3)

还有多种其他辅助方法,例如用于组合框(同时链接错误消息)。你可以在这里找到一个很好的概述:Box Cheat Sheet for Lift

您可以单独使用它,无需使用整个电梯框架。对于上面的示例,我使用了以下 sbt 脚本:

scalaVersion := "2.9.1"

libraryDependencies += "net.liftweb" %% "lift-common" % "2.5-RC2"

libraryDependencies += "net.liftweb" %% "lift-util" % "2.5-RC2"

【讨论】:

    【解决方案4】:

    作为对 Impredicative 的回答和评论的补充,如果您的依赖项中同时包含 scalaz-sevenscalaz-contrib/scala210:

    > scala210/console
    [warn] Credentials file /home/folone/.ivy2/.credentials does not exist
    [info] Starting scala interpreter...
    [info] 
    Welcome to Scala version 2.10.0 (OpenJDK 64-Bit Server VM, Java 1.7.0_17).
    Type in expressions to have them evaluated.
    Type :help for more information.
    
    scala> import scala.util._
    import scala.util._
    
    scala> def map(l:List[Int]): List[Try[Int]] = l map {
         |   case 4 => Failure(new Exception("failed"))
         |   case i => Success(i)
         | }
    map: (l: List[Int])List[scala.util.Try[Int]]
    
    scala> import scalaz._, Scalaz._
    import scalaz._
    import Scalaz._
    
    scala> import scalaz.contrib.std.utilTry._
    import scalaz.contrib.std.utilTry._
    
    scala> val l1 = List(1,2,3,4,5,6)
    l1: List[Int] = List(1, 2, 3, 4, 5, 6)
    
    scala> map(l1).sequence
    res2: scala.util.Try[List[Int]] = Failure(java.lang.Exception: failed)
    
    scala> val l2 = List(1,2,3,5,6)
    l2: List[Int] = List(1, 2, 3, 5, 6)
    
    scala> map(l2).sequence
    res3: scala.util.Try[List[Int]] = Success(List(1, 2, 3, 5, 6))
    

    您需要 scalaz 来获取 ListApplicative instance(隐藏在 MonadPlus 实例中),以获取 sequence 方法。对于TryTraverse instance,您需要scalaz-contrib,这是sequence 的类型签名所必需的。 Try 存在于 scalaz 之外,因为它只出现在 scala 2.10 中,而 scalaz 旨在交叉编译到早期版本。

    【讨论】:

      【解决方案5】:

      这对于故障优先操作来说非常接近于最低限度:

      def something[A](xs: Seq[Try[A]]) =
        Try(xs.map(_.get))
      

      (到了您不应该费心创建方法的地步;只需使用Try)。如果你想要所有的失败,一个方法是合理的;我会使用Either

      def something[A](xs: Seq[Try[A]]) =
        Try(Right(xs.map(_.get))).
        getOrElse(Left(xs.collect{ case Failure(t) => t }))
      

      【讨论】:

      • 不错! (尽管它会重新抛出异常,如果有的话)你将如何扩展它以保留所有异常?
      • @mhs - 如果失败不常见或性能不重要,您需要重新抛出,因为这是处理问题的最简单方法。异常的堆栈跟踪已经生成,因此 throw/catch 本身并不太昂贵。如果我想保留所有异常,我会将它们打包在 Either 中,例如就像我的编辑一样。
      【解决方案6】:

      这是我的 2cents:

      def sequence[A, M[_] <: TraversableOnce[_]](in: M[Try[A]])
        (implicit cbf:CanBuildFrom[M[Try[A]], A, M[A]]): Try[M[A]] = {
          in.foldLeft(Try(cbf(in))) {
            (txs, tx) =>
              for {
                xs <- txs
                x <- tx.asInstanceOf[Try[A]]
              } yield {
                xs += x
              }
          }.map(_.result())
        }
      

      【讨论】:

        【解决方案7】:

        Scala 2.13 开始,大多数集合都提供了partitionMap 方法,该方法根据返回RightLeft 的函数对元素进行分区。

        在我们的例子中,我们可以调用partitionMap,并使用一个函数将我们的Trys 转换为Eithers (Try::toEither),以便将Successes 划分为Rights 和Failures作为Lefts.

        那么这只是一个根据是否有左来匹配得到的左和右分区元组的问题:

        tries.partitionMap(_.toEither) match {
          case (Nil, rights)       => Success(rights)
          case (firstLeft :: _, _) => Failure(firstLeft)
        }
        // * val tries = List(Success(10), Success(20), Success(30))
        //       => Try[List[Int]] = Success(List(10, 20, 30))
        // * val tries = List(Success(10), Success(20), Failure(new Exception("error1")))
        //       => Try[List[Int]] = Failure(java.lang.Exception: error1)
        

        中间partitionMap步骤详情:

        List(Success(10), Success(20), Failure(new Exception("error1"))).partitionMap(_.toEither)
        // => (List[Throwable], List[Int]) = (List(java.lang.Exception: error1), List(10, 20))
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-12-08
          • 1970-01-01
          • 1970-01-01
          • 2013-02-12
          • 2014-05-28
          • 2018-04-15
          • 2017-04-23
          • 2015-04-18
          相关资源
          最近更新 更多