展平Scala尝试

J P*_*lar 16 scala try-catch scala-2.10

是否有一种简单的方法可以展平一系列试验,以使试验值成功,或者只是失败?例如:

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))
Run Code Online (Sandbox Code Playgroud)

您可以如何处理集合中的多个故障?

Rex*_*err 26

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

def something[A](xs: Seq[Try[A]]) =
  Try(xs.map(_.get))
Run Code Online (Sandbox Code Playgroud)

(到了你不应该打扰创建方法的地步;只需使用Try).如果你想要所有的失败,一种方法是合理的; 我用的是Either:

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


Imp*_*ive 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))
}
Run Code Online (Sandbox Code Playgroud)

结果:

sequence(l1)
Run Code Online (Sandbox Code Playgroud)

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

sequence(l2)
Run Code Online (Sandbox Code Playgroud)

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


fol*_*one 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))
Run Code Online (Sandbox Code Playgroud)

你需要scalaz获得一个Applicative 实例List(藏在MonadPlus实例),以获得sequence方法.您需要scalaz-的contrib的Traverse 实例Try,这是由需要sequence的类型签名. Try生活在scalaz之外,因为它只出现在scala 2.10中,而scalaz旨在交叉编译到早期版本).


Mal*_*off 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))
Run Code Online (Sandbox Code Playgroud)

请注意,使用partition并不像它所获得的类型安全,正如@unchecked注释所见.在这方面,a foldLeft积累两个序列Seq[Success[T]]并且Seq[Failure[T]]会更好.

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

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))
Run Code Online (Sandbox Code Playgroud)


Xav*_*hot 5

Starting in Scala 2.13, most collections are provided with a partitionMap method which partitions elements based on a function returning either Right or Left.

In our case we can call partitionMap with a function that transforms our Trys into Eithers (Try::toEither) in order to partition Successes as Rights and Failures as Lefts.

Then it's just a matter of matching the resulting partitioned tuple of lefts and rights based on whether or not there are 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)
Run Code Online (Sandbox Code Playgroud)

Details of the intermediate partitionMap step:

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