Scala`map`但在`Failure`早期退出

时间:2016-10-25 00:37:02

标签: scala

如果我有Seq,我可以map覆盖它。

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

但有时,传递给map的函数可能会失败。

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

最后一个将返回Seq[Try[Int]]。我真正想要的是Try[Seq[Int]],如果任何一个映射是Failure,它将停止迭代并返回Failure。如果没有错误,我希望它只返回所有已转换的元素,从Try解压缩。

Scala的惯用方法是什么?

2 个答案:

答案 0 :(得分:4)

你可能会过度思考这个问题。您map中的匿名函数与Try.apply基本相同。如果您希望以Try[Seq[Int]]结尾,那么您可以将Seq包裹在Try.applymap内:

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

如果toInt中的任何一个失败,它将抛出异常并停止执行,并成为Failure

答案 1 :(得分:1)

不确定它是否具有惯用性,但我会做这样的事情:

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

如果你真的想提前退出而不是跳过元素,你可以使用旧的递归:

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]())
}