Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 408
J Pullar Avatar asked Mar 19 '13 09:03

J Pullar


People also ask

What does flatten do in Scala?

The flatten function is applicable to both Scala's Mutable and Immutable collection data structures. The flatten method will collapse the elements of a collection to create a single collection with elements of the same type.

What is try in Scala?

The Try type represents a computation that may either result in an exception, or return a successfully computed value. It's similar to, but semantically different from the scala. util. Either type. Instances of Try[T] , are either an instance of scala.


4 Answers

This is pretty close to minimal for fail-first operation:

def something[A](xs: Seq[Try[A]]) =
  Try(xs.map(_.get))

(to the point where you shouldn't bother creating a method; just use Try). If you want all the failures, a method is reasonable; I'd use an Either:

def something[A](xs: Seq[Try[A]]) =
  Try(Right(xs.map(_.get))).
  getOrElse(Left(xs.collect{ case Failure(t) => t }))
like image 192
Rex Kerr Avatar answered Oct 23 '22 11:10

Rex Kerr


A little less verbose, and more type safe:

def sequence[T](xs : Seq[Try[T]]) : Try[Seq[T]] = (Try(Seq[T]()) /: xs) {
    (a, b) => a flatMap (c => b map (d => c :+ d))
}

Results:

sequence(l1)

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

sequence(l2)

res9: scala.util.Try[Seq[Int]] = Success(List(1, 2, 3, 5, 6))

like image 24
Impredicative Avatar answered Oct 23 '22 11:10

Impredicative


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))
like image 8
Malte Schwerhoff Avatar answered Oct 23 '22 10:10

Malte Schwerhoff


As an addition to Impredicative's answer and comment, if you have both scalaz-seven and scalaz-contrib/scala210 in your dependencies:

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

You need scalaz to get an Applicative instance for the List (hidden in the MonadPlus instance), to get the sequence method. You need scalaz-contrib for the Traverse instance of Try, which is required by the sequence's type signature. Try lives outside of scalaz, since it only appeared in scala 2.10, and scalaz aims to cross-compile to earlier versions).

like image 6
George Avatar answered Oct 23 '22 09:10

George