【问题标题】:In Scala, process a list of items with a Try operation, and keep the original item to report the possible failure在 Scala 中,使用 Try 操作处理项目列表,并保留原始项目以报告可能的失败
【发布时间】:2016-10-14 12:55:55
【问题描述】:

我有一个字符串列表,我想对每个项目进行几次转换。我想保留原始字符串,以便在它无效时显示它。示例如下:

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

【问题讨论】:

  • 在第二条评论中,我认为“所以我不能举报”应该是“所以我可以举报”
  • 谢谢@pascal-soucy,已修复。
  • 我可能只是定义一个错误案例类,该类捕获底层异常和原始字符串。您将其映射到 parseXML 中的失败端,然后像往常一样使用您的尝试。
  • 是的,但我需要在parseXML、id.toInt 和许多其他地方这样做。太丑了。
  • @DavidPortabella 好吧,您可以使用Writer(或或多或少等效于Tuple2[String, ?] 的monad),它可以让您根据需要链接您的地图,但list.traverse(...) 等不会没有正确的语义——你必须在排序之前兑现每个写入器操作。

标签: scala scalaz


【解决方案1】:

这可以通过添加一个额外的 trait ParseContext[T] 优雅地解决,除了原始上下文,例如行号和“原始”文本之外,它是一个 Functor (.map(f: T =&gt; T)) 和一个 Monad (.flatMap(f: T =&gt; ParseContext[T]) )。您可以选择创建两个ParseContext 实例,分别命名为Success 和Failure,以指示解析过程中可能出现的故障。

基本上,您可以使用一些额外的上下文信息来扩展 Try 特征。

【讨论】:

  • 这个 Try 扩展是否已经在 scalaz 或其他地方实现? scalaZ State 会是这个扩展,还是完全不同? eed3si9n.com/learning-scalaz/State.html
  • 我不认为 scalaz State 真的适合这个问题。该解决方案易于实现,因此最好先尝试实现它,然后查看它匹配的代数数据类型。
  • 我宁愿避免重新发明轮子。
  • 然后将我们所有人指向您不希望重新发明的轮子。
  • 正如我在对问题的最后评论中所说:ScalaZ Writer 是我曾经看过的,我认为它在这里可能有用,但我不记得名字了。但是,我看到它导致Writer[List[String], ?],而不是Writer[String, ?]。我想我的情况太具体了,我必须按照@Edwing 的建议自己实施。虽然看起来并不容易;稍后我会看看这个。
【解决方案2】:

也许,将您的多个 maps 合并为一个会有所帮助?

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

【讨论】:

  • 我的小例子确实可以折叠成一个地图操作;但这不是我要求的原因。
  • 那你要求什么是?
  • 如何进行多个映射操作,同时以隐式方式(而不是显式方式)携带原始字符串。看看 Edwin 的答案。
  • 我以为你想避免重新发明轮子;)
  • 我愿意;是什么让你有相反的想法?
【解决方案3】:

如果列表中没有重复的字符串,你可以把它做成一个地图,然后mapValues覆盖它:

(l zip l toMap)
   .mapValues(parseXML)
   .mapValues(tryEntry => tryEntry.map(entry => (entry \ "@id").text))
   .mapValues(tryId => tryId.flatMap(id => Try(id.toInt)))
   .toList

【讨论】:

  • 您可以随时删除:(l zipWithIndex) zip l toMap
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-02-18
  • 1970-01-01
  • 1970-01-01
  • 2016-03-15
  • 1970-01-01
  • 2018-12-13
  • 2015-06-14
相关资源
最近更新 更多