Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala - try-catch inside for loop with yield

I'm writing a Scala app using some 3rd party library. When iterating over a collection from that library an exception occurs, which I want to ignore, and go on with the iteration. The whole thing is inside a for loop with yield.

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

As said, the error occurs inside the iteration, so this line:

mailing <- mailingCollection

If I would put a try catch around the whole loop, then I cannot continue with the iteration. I have a non-functional solution to have the same output as above, but I want to keep the whole app in a functional style. This is what I came up within a non-functional way:

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

My question is, do you know a functional way of trying to iterate through a for loop and on error skipping that element and only returning the elements where the iteration worked?

Thanks, Felix

like image 617
Phoen Avatar asked Sep 14 '26 02:09

Phoen


1 Answers

If you want to remain functional then you need a recursive function to unpick the unreliable iterator.

This is untested code, but it might look like this:

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

You can check each Try value to see which iterations succeeded or failed. It you just want the success values then you can call .flatMap(_.toOption) on the List. Or use this version of 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)
}

Someone smarter than me can probably make this return another Iterator rather than a List.

like image 99
Tim Avatar answered Sep 16 '26 19:09

Tim



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!