scalatry-catchscala-2.10

Flatten Scala Try


Is there a simple way to flatten a collection of try's to give either a success of the try values, or just the failure? For example:

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

And can how would you handle multiple Failures in the collection?


Solution

  • Maybe not as simple as you hoped for, but this works:

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

    Note that the use of partition is not as type safe as it gets, as witnessed by the @unchecked annotations. In that respect, a foldLeft that accumulates two sequences Seq[Success[T]] and Seq[Failure[T]] would be better.

    If you wanted to keep all failures, you can use this:

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