有没有一种简单的方法来展平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))
您可以如何处理集合中的多个失败?
答案 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-seven和scalaz-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
注释所证明的那样安全。在这方面,积累两个序列foldLeft
和Seq[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
方法,该方法根据返回Right
或Left
的函数对元素进行分区。
在我们的情况下,我们可以使用将partitionMap
s转换为Try
s(Try::toEither
)的函数来调用Either
,以便对Success
es进行分区分别为Right
和Failure
。
这是一个简单的问题,即根据是否有剩余来匹配结果的左和右分区元组:
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