展平Scala尝试

时间:2013-03-19 09:29:01

标签: scala try-catch scala-2.10

有没有一种简单的方法来展平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))

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

7 个答案:

答案 0 :(得分:26)

对于失败优先操作,这非常接近最小值:

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

答案 1 :(得分:9)

稍微冗长一点,更安全:

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]] =失败(java.lang.Exception:失败)

sequence(l2)
  

res9:scala.util.Try [Seq [Int]] =成功(列表(1,2,3,5,6))

答案 2 :(得分:6)

作为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才能获得Applicative List {隐藏在MonadPlus实例中)的Traverse instance,以获取sequence方法。您需要Try sequence Try的{​​{1}} scalaz-contrib,这是{{1}}类型签名所必需的。 {{1}}生活在scalaz之外,因为它只出现在scala 2.10中,而scalaz旨在交叉编译为早期版本。)

答案 3 :(得分:5)

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

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注释所证明的那样安全。在这方面,积累两个序列foldLeftSeq[Success[T]]的{​​{1}}会更好。

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

Seq[Failure[T]]

答案 4 :(得分:3)

看看liftweb Box monad。借助tryo构造函数,它可以为您提供所需的抽象。

使用tryo,您可以将功能提升为Box。然后该框包含函数的结果或包含错误。然后,您可以使用常用的monadic辅助函数(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"

答案 5 :(得分:0)

这些是我的2:

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

答案 6 :(得分:0)

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

在我们的情况下,我们可以使用将partitionMap s转换为Try s(Try::toEither)的函数来调用Either,以便对Success es进行分区分别为RightFailure

这是一个简单的问题,即根据是否有剩余来匹配结果的左和右分区元组:

Left

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