【问题标题】:Scala - try-catch inside for loop with yieldScala - 在 for 循环内尝试捕获并产生
【发布时间】:2019-10-29 01:46:15
【问题描述】:

我正在使用一些 3rd 方库编写一个 Scala 应用程序。当迭代该库中的集合时,会发生异常,我想忽略它,然后继续迭代。整个事情都在一个带有 yield 的 for 循环中。

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

如前所述,错误发生在迭代内部,所以这一行:

mailing <- mailingCollection

如果我在整个循环中放置一个 try catch,那么我将无法继续迭代。我有一个非功能性解决方案具有与上述相同的输出,但我想让整个应用程序保持功能性风格。这是我想出的一种非功能性方式:

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 循环并在出错时跳过该元素并仅返回迭代有效的元素的功能方法?

谢谢, 费利克斯

【问题讨论】:

  • 什么是mailingCollection?如果它只是一个集合,则不能在该行中引发错误(除非它是您自己的集合,它会在 map 上引发错误。也许 getNamegetSubject 会引发异常?
  • 嘿 Krzysztof,mailingCollection 是来自该外部库的一个类的对象。所以我认为实际的错误在于他们的迭代实现。但我只想跳过那个错误。
  • mailingCollection 大吗?您需要懒惰地处理它,还是可以将其加载到列表中?你能分享mailingCollection的类型吗?
  • 我不确定如何“跳过”迭代。我的意思是,我想如果你从迭代器中得到一个异常,你不能再调用.next 现在得到一个元素,不是吗?也许您所说的“继续迭代”只是指在不引发异常的情况下处理错误?

标签: scala for-loop try-catch yield


【解决方案1】:

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

正如我在评论中提到的,我假设“继续迭代”是指不抛出异常并在此之前仍然返回结果。我还将假设 您想保持 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 怎么办?如果您真正想要的只是一个包含所有成功返回的Mailings 的List[(String, Int)] 怎么办?在这种情况下,你会这样做:

val flattened: Iterator[(String, String)] = myFuntionalSequence.flatten

现在是真正功能部分;)

但是,如果您真的想知道是否存在异常怎么办?也就是说,如果至少有一个异常,您希望返回 None,否则返回 Some(List(someMailing1Pair, etc....))。基本上,您想要的是一种将List[Option[(String, String)]] 转换为Option[List[(String, String)]] 的方法。

输入 Traverse,来自 Cats 库:

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。这种方法是通用的,它适用于实现Traverse 的任何东西,我建议您阅读我链接的文档:您会发现很多好东西! :)

【讨论】:

  • 第一个表达式不会返回List,而是返回Option
  • 是的,我意识到我假设迭代已经在一个单独的行中。将修复答案
【解决方案2】:

如果你想保持功能,那么你需要一个递归函数来取消选择不可靠的迭代器。

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

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 值以查看哪些迭代成功或失败。如果您只想要成功值,那么您可以在List 上调用.flatMap(_.toOption)。或者使用这个版本的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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-02-07
    • 2013-12-15
    • 2022-08-13
    • 2023-04-02
    • 2014-03-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多