Scala-在内部尝试捕获以产生收益

时间:2019-06-14 12:06:38

标签: scala for-loop try-catch yield

我正在使用一些第三方库编写一个Scala应用。当从该库中迭代一个集合时,会发生一个异常,我想忽略该异常,然后继续进行迭代。整个过程都在一个带有yield的for循环中。

val myFuntionalSequence = for {
  mailing <- mailingCollection
} yield (mailing.getName, mailing.getSubject)

如前所述,该错误发生在迭代内部,因此此行:

mailing <- mailingCollection

如果我要尝试遍历整个循环,则无法继续进行迭代。我有一个非功能性的解决方案,具有与上述相同的输出,但是我想让整个应用程序保持功能性风格。这是我通过非功能性方式想到的:

case class MyElement(name: String, subject: String)

...

var myNonFunctionalList = scala.collection.mutable.ListBuffer[MyElement]()

while(mailingIterator.hasNext) {
  try {
    val mailing = mailingIterator.next()
    myNonFunctionalList += MyElement(mailing.getName, mailing.getSubject)
  } catch {
    case e: Exception => println("Error")
  }
}

我的问题是,您是否知道尝试遍历for循环并在跳过该元素并仅返回迭代起作用的元素时出错的功能方法?

谢谢, 费利克斯

2 个答案:

答案 0 :(得分:1)

更新:正如Tim在评论中指出的那样,我更新了答案以纠正我对该问题的误解。

正如我在评论中提到的那样,我将假设通过“继续迭代”,您的意思是不抛出异常,并且仍然返回结果。我还将假设您要保持Iterator 的惰性。

如果是这样,我将有效地装饰java.util.Iterator来防止它们的错误,但是返回一个Scala Iterator,以便您仍然拥有原始库需要的惰性。在以下行上的内容(未准备就绪):

def scalaIterator[A](it: java.util.Iterator[A]): Iterator[Option[A]] = new Iterator[Option[A]](){
  private var hasBoomed: Boolean = false
  override def hasNext = !hasBoomed && it.hasNext

  override def next() = {
    Try(it.next()) match {
      case Failure(_) =>
        hasBoomed = true
        None
      case Success(value) => Some(value)
    }
  }
}

现在,假设您的库调用返回了Mailing实例,则应该将其包装在Option中:

val myFuntionalSequence: Iterator[Option[(String, String)]] = for{
  mailingOpt <- scalaIterator(thirdPartyIterator)
} yield mailingOpt.map(mailing => (mailing.name, mailing.subject))

这将返回Iterator[Option[(String, Int)]]

示例结果可能是(实现时):

Some(mailing1Pair),Some(mailing2Pair),None // in case of an exception
Some(mailing1Pair),Some(mailing2Pair),Some(mailing3Pair) // no exception 

现在,如果您实际上不在乎最后的None怎么办?而且,如果您真正想要的是一个包含所有成功返回的List[(String, Int)]的{​​{1}}呢?在这种情况下,您可以这样做:

Mailing

现在是 really 功能部分;)

但是,如果您实际上想知道是否存在异常,该怎么办?也就是说,如果存在至少一个异常,则要返回val flattened: Iterator[(String, String)] = myFuntionalSequence.flatten ,否则,要返回Some(List(someMailing1Pair等...))。基本上,您想要的是将None转换为List[Option[(String, String)]]的方法。

从Cats库中输入Traverse

Option[List[(String, String)]]

...此时,您将其与其他implicit cats.implicits._ val myFuntionalSeuence: Iterator[Option[(String, String)]] = ... val flipped: Option[List[(String, String)]] = myFuntionalSeuence.toList.sequence //notice the .toList here: we are materializing a lazy collection here 一样对待。这种方法是通用的,它可以与任何实现Option的方法一起使用,我建议您阅读链接的文档:您会发现很多好东西! :)

答案 1 :(得分:1)

如果您想保持功能正常,则需要递归函数来取消不可靠的迭代器。

这是未经测试的代码,但可能看起来像这样:

def safeIterate[T](i: Iterator[T]): List[Try[T]] = {
  @annotation.tailrec
  def loop(res: List[Try[T]]): List[Try[T]] =
    if (i.hasNext) {
      loop(Try(i.next) +: res)
    } else {
      res.reverse
    }

  loop(Nil)
}

您可以检查每个Try值以查看哪些迭代成功或失败。如果只需要成功值,则可以在.flatMap(_.toOption)上调用List。或使用此版本的safeIterate

def safeIterate[T](i: Iterator[T]): List[T] = {
  @annotation.tailrec
  def loop(res: List[T]): List[T] =
    if (i.hasNext) {
      Try(i.next) match {
        case Success(t) => loop(t +: res)
        case _ => loop(res)
      }
    } else {
      res.reverse
    }

  loop(Nil)
}

比我聪明的人可能会使此返回值返回另一个Iterator而不是List