Scala - попробуй ловить внутри для цикла с выходом - PullRequest
2 голосов
/ 14 июня 2019

Я пишу приложение Scala, используя стороннюю библиотеку. При выполнении итерации по коллекции из этой библиотеки возникает исключение, которое я хочу игнорировать и продолжаю итерацию. Все это внутри цикла 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 и пропуска этого элемента по ошибке и возврата только тех элементов, где работала итерация?

Спасибо, Felix

Ответы [ 2 ]

1 голос
/ 14 июня 2019

Если вы хотите сохранить работоспособность, тогда вам нужна рекурсивная функция для снятия ненадежного итератора.

Это непроверенный код, но он может выглядеть так:

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.

1 голос
/ 14 июня 2019

ОБНОВЛЕНИЕ: я обновил ответ, чтобы исправить мое неправильное понимание вопроса, как указал Тим в своем комментарии.

Как уже упоминалось в моем комментарии, я буду считать, что под «продолжить итерацию» вы подразумеваете просто не выбрасывать исключение и по-прежнему возвращать результаты вплоть до этого момента. Я также предполагаю, что вы хотите поддерживать лень 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)], который содержит все успешно возвращенные Mailing с? В этом случае вы должны сделать:

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, и я рекомендую вам прочитать документы, на которые я ссылаюсь: вы найдете много вкусностей! :)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...