我正在使用一些第三方库编写一个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循环并在跳过该元素并仅返回迭代起作用的元素时出错的功能方法?
谢谢, 费利克斯
答案 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
。