3

If I have a Seq, I can map over it.

val ss = Seq("1", "2", "3")
println(ss.map(s => s.toInt))  // List(1, 2, 3)

But sometimes, the function that you pass to map can fail.

val ss = Seq("1", "2", "c")
println(ss.map(s => try { Success(s.toInt) } catch { case e: Throwable  => Failure(e) }))  // List(Success(1), Success(2), Failure(java.lang.NumberFormatException: For input string: "c"))

This last one will return a Seq[Try[Int]]. What I really want though is a Try[Seq[Int]], where if any one of the mapping is a Failure, it stops the iteration and returns the Failure instead. If there is no error, I want it to just return all the converted elements, unpacked from the Try.

What is the idiomatic Scala way to do this?

math4tots
  • 8,540
  • 14
  • 58
  • 95

2 Answers2

6

You may be overthinking this. The anonymous function in your map is essentially the same as Try.apply. If you want to end up with Try[Seq[Int]] then you can wrap the Seq in Try.apply and map within:

scala> val ss = Try(Seq("1", "2", "c").map(_.toInt))
ss: scala.util.Try[Seq[Int]] = Failure(java.lang.NumberFormatException: For input string: "c")

If any of the toInts fails, it will throw an exception and stop executing, and become a Failure.

Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
  • 1
    Ah that's cool, thank you. But what if instead of `_.toInt`, I was using a function that would return a `Try[B]` instead of actually throwing an exception? Would that work too? I'm new to Scala and I can't seem to find `apply` in the [docs](http://www.scala-lang.org/api/2.9.3/scala/util/Try.html) ^_^; – math4tots Oct 25 '16 at 00:51
  • 1
    @math4tots Click the big "C" and "O" in the scaladoc to switch between class and companion object: http://www.scala-lang.org/api/current/index.html#scala.util.Try$@apply[T](r:=>T):scala.util.Try[T] – Michael Zajac Oct 25 '16 at 00:54
4

Not sure it's idiomatic, but I would do something like this:

import util.{Try, Success, Failure}
import collection.mutable.ListBuffer

def toInt(s: String) =
  // Correct usage would be Try(s.toInt)
  try {
    Success(s.toInt)
  } 
  catch { 
    case e: Throwable  => Failure(e)
  }

def convert[A](ss: Seq[String], f: String => Try[A]) = {
  ss.foldLeft(Try(ListBuffer[A]())) { 
    case (a, s) =>
      for {
        xs <- a
        x  <- f(s)
      }
      yield xs :+ x
  }.map(_.toSeq)
}

scala> convert(List("1", "2"), toInt)
scala.util.Try[Seq[Int]] = Success(List(1, 2))

scala> convert(List("1", "c"), toInt)
scala.util.Try[Seq[Int]] = Failure(java.lang.NumberFormatException: For input string: "c")

If you really want to exit early instead of skipping elements you can use good old recursion:

def convert[A](ss: Seq[String], f: String => Try[A]) = {

  @annotation.tailrec
  def loop(ss: Seq[String], acc: ListBuffer[A]): Try[Seq[A]] = {
    ss match {
      case h::t =>
        f(h) match {
          case Success(x) => loop(t, acc :+ x)
          case Failure(e) => Failure(e)
        }
      case Nil =>
        Success(acc.toSeq)

    }
  }

  loop(ss, ListBuffer[A]())
}
Victor Moroz
  • 9,167
  • 1
  • 19
  • 23
  • Thanks, you're the only one who's actually trying to address the spirit of my question. I had also thought of using a `foldLeft`, but I was hoping that converting an iterable of tries to a try of iterables was a common pattern in functional programming. – math4tots Oct 25 '16 at 17:38
  • It is a well-known pattern in FP, only `sequence` from Scalaz doesn't work with `Try`, for `Option` it would be `List(1.some, 2.some).sequence //> Option[List[Int]] = Some(List(1, 2))`. – Victor Moroz Oct 25 '16 at 17:45