Reputation: 14803
With Futures
there is an easy way to transform Seq[Future]
to a Future[Seq]
:
Future.sequence(seqOfFutures)
I could not find an analog thing with Try
.
It works with foldLeft
but what I really like would have something like Try.sequence(seqOfTry)
.
Is there a reason that such a function is not provided?
How is this done properly?
Semantics:
A List of the values on Success: Success(Seq(1,2,3,4))
For Failure there are 2 possibilities:
Fails on the fist Failure
and returns it. This is handled by this question: listtryt-to-trylistt-in-scala
Gathers all Failures
and returns a 'compound' Failure.
Is there also a solution for the 'compound' Failure?
Upvotes: 2
Views: 2499
Reputation: 4039
Another version in case you want to return failure on the first error encountered, sharing my teammates util function here
// Reduces many Trys into a single Try by transforming a Seq[Try[_ <: T]] into a Try[Seq[T]].
// If any of the Trys are Failure, then this returns a Failure with the first error encountered
def sequenceTrys[T](trySequence: Seq[_ <: Try[_ <: T]]): Try[Seq[T]] = {
trySequence.foldLeft(Try(Seq.empty[T])) {
(acc, tryElement) => acc.flatMap(accSeq => tryElement.map(success => accSeq :+ success))
}
}
Upvotes: 1
Reputation: 48420
As per Luis' suggestion Validated
is designed for error accumulation so consider traverse
like so
la.traverse(_.toEither.toValidatedNec)
lb.traverse(_.toEither.toValidatedNec)
which outputs
res2: cats.data.ValidatedNec[Throwable,List[Int]] = Invalid(Chain(java.lang.RuntimeException: boom, java.lang.RuntimeException: crash))
res3: cats.data.ValidatedNec[Throwable,List[Int]] = Valid(List(1, 2, 3))
where
import cats.syntax.traverse._
import cats.instances.list._
import cats.syntax.either._
import scala.util.{Failure, Success, Try}
val la: List[Try[Int]] = List(Success(1), Success(2), Failure(new RuntimeException("boom")), Success(3), Failure(new RuntimeException("crash")))
val lb: List[Try[Int]] = List(Success(1), Success(2), Success(3))
Without error accumulation we could just sequence like so
import cats.implicits._
la.sequence
which outputs
res0: scala.util.Try[List[Int]] = Failure(java.lang.RuntimeException: boom)
Upvotes: 5
Reputation: 27356
This is a solution to the second question.
case class CompoundError(errs: List[Throwable]) extends Throwable
def toTry[T](list: List[Try[T]]): Try[List[T]] =
list.partition(_.isSuccess) match {
case (res, Nil) =>
Success(res.map(_.get))
case (_, errs) =>
Failure(CompoundError(errs.collect { case Failure(e) => e }))
}
The partition
operation separates the successes and failures, and the match
returns the appropriate value depending on whether there are any failures or not.
Previous solution:
case class CompoundError(errs: List[Throwable]) extends Throwable
def toTry[T](list: List[Try[T]]): Try[List[T]] = {
val (res, errs) = list.foldLeft((List.empty[T], List.empty[Throwable])) {
case ((res, errs), item) =>
item match {
case Success(t) => (t :: res, errs)
case Failure(e) => (res, e :: errs)
}
}
errs match {
case Nil => Success(res.reverse)
case _ => Failure(CompoundError(errs.reverse))
}
}
Upvotes: 6