В Scala обработайте список элементов с помощью операции Try и сохраните исходный элемент, чтобы сообщить о возможном сбое.

У меня есть список строк, и я хочу сделать несколько преобразований для каждого элемента. Я хочу сохранить исходную строку, чтобы я мог показать ее, если она была недействительной. Пример следующим образом:

import scala.util.Try

val list = List("<entry id='1'/>", "haha", "<entry id='hehe'/>")

def parseXML(str: String) = Try { xml.XML.loadString(str) }

list
  .map(parseXML)
  .map(tryEntry => tryEntry.map(entry => (entry \ "@id").text))
  .map(tryId => tryId.flatMap(id => Try(id.toInt)))

// here I lose the original string
res17: List[Try[Int]] = List(
  Success(1),
  Failure(org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.),
  Failure(java.lang.NumberFormatException: For input string: "hehe")
)

// here I keep a copy of the original string, so I can report the invalid entry to the user
list
  .map(l => (l, parseXML(l)))
  .map { case(line, tryEntry) => (line, tryEntry.map(entry => (entry \ "@id").text)) }
  .map { case(line, tryId) => (line, tryId.flatMap(id => Try(id.toInt))) }

res19: List[(String, Try[Int])] = List(
  ("<entry id='1'/>", Success(1)),
  ("haha", Failure(org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.)),
  ("<entry id='hehe'/>", Failure(java.lang.NumberFormatException: For input string: "hehe"))
)

В res19 я сохраняю копию исходной строки, чтобы я мог сообщить об ошибке и исходной строке. Тем не менее, мне нужно нести эту информацию каждый раз во время операции отображения, и это ужасно. Есть ли способ лучше? (возможно, используя ScalaZ State а также for?)

3 ответа

Решение

Это можно элегантно решить, добавив дополнительную черту ParseContext[T], который, кроме исходного контекста, такого как номер строки и "оригинальный" текст, является Функтором (.map(f: T => T)) и монаду (.flatMap(f: T => ParseContext[T])). При желании вы можете сделать два экземпляра ParseContextпо имени Success а также Failure, чтобы указать на возможный сбой при разборе.

По сути, вы расширяете Try черта с некоторой дополнительной контекстной информацией.

Может быть, рушится ваш кратный mapс одной поможет?

 val result: List[(String, Try[Int])] = list
   .map { line =>
      line -> parseXML(line)
        .map(_ \ "@id")
        .text
        .flatMap(id => Try(id.toInt)
   }

Если в списке нет повторяющихся строк, вы можете сделать это картой, а затем mapValues над ним:

(l zip l toMap)
   .mapValues(parseXML)
   .mapValues(tryEntry => tryEntry.map(entry => (entry \ "@id").text))
   .mapValues(tryId => tryId.flatMap(id => Try(id.toInt)))
   .toList
Другие вопросы по тегам