pme
pme

Reputation: 14803

How to transform a Seq[Try] to a Try[Seq]

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:

Is there also a solution for the 'compound' Failure?

Upvotes: 2

Views: 2499

Answers (3)

raksja
raksja

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

Mario Galic
Mario Galic

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

Tim
Tim

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

Related Questions