【问题标题】:Most idiomatic way to write try/catch/finally in Scala?在 Scala 中编写 try/catch/finally 的最惯用方式?
【发布时间】:2016-12-15 06:20:12
【问题描述】:

在 Scala 中编写以下代码的最佳方式是什么?它看起来不太对我 - 首先是 2 个 val 的前向声明,然后是长的 PrintWriter 创建行,然后是 finallyblock。唯一惯用的就是 catch 块...

val outputStream = Try(fs.create(tmpFile))
val writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream.get)))

if (outputStream.isFailure) {
  logger.error(s"Couldn't open file: $tmpFile")
}

try {

  features.foreach {
    case (sectionName, modelRDD) =>
      writer.append("{" + sectionName + ", " + modelRDD.getNumPartitions + "}")
  }

} catch {
  case e: Exception =>
    logger.error(s"Got exception", e)
    throw e

} finally {
  outputStream.get.close()
  writer.close()
}

【问题讨论】:

  • 在什么基础上他们不“看起来很合适”?
  • 我是 Scala 新手。也许这没问题,但正如我所提到的,我希望有一些更简单的东西:2 vals 声明、编写器创建行和finally 块。也许我可以学习一些更惯用的东西?

标签: scala


【解决方案1】:

我们可以进一步使用初始Try的上下文来执行完整的I/O操作:

首先,我们定义一个封装我们流程的函数:

def safeFilePrint(tf: => OutputStream)(op: PrintWriter => Unit): Try[Unit] = {
  val os = Try(tf) 
  val write = {
      val writer = os.map(f => new PrintWriter(f))
      val writeOp = writer.map(op)
      val flushOp = writer.map(_.flush)
      writeOp.flatMap(_ => flushOp)
  } 
  val close = os.map(_.close)
  write.flatMap(_ => close)
}

及用法:

val collection = Seq(...)
val writeResult = safeFilePrint(new FileOutputStream(new File("/tmp/foo.txt"))){w => 
    collection.foreach(elem => w.write(e)
}

请注意,与原始代码相比,我们有一个写操作的结果。如果一切顺利,writeResult 将是 Success(()),或者 Failure(exception) 出现问题。基于此,我们的应用程序可以进一步决定要做什么。

有人可能想知道:finally 在哪里?” 在 Java 中,finally 用于确保执行某些代码(通常是资源管理),即使在以下情况下在try 范围内抛出的异常将导致遵循异常处理路径。

在 Scala 中,使用 TryEither 或我们自己的 ADT 等结构,我们将错误处理提升到应用程序级别。 finally 变得不必要了,因为我们的程序能够将失败作为程序的另一个有效状态来处理。

【讨论】:

  • Try 懒惰吗? close 操作似乎顺序错误,否则
  • 我想我只是不够精通 Scala。看起来_.close 可以在op 之前运行。为什么将flatMap 与常量函数一起使用?不应该是类似os.map(new PrintWriter(_)).map(op).flatMap(_=> os.map(_.close))的东西吗?
  • @Bergi Try 很渴望。 write 的值将在close 的值之前计算。那个紧凑的表达式很诱人,但是如果我们这样做并且op 失败并返回Exception,我们是否曾经执行过close?我认为这种方式不会保留原始问题中 finally 的语义。我最后使用flatmap来组合closewrite的结果,所以只有当两者都是Success时,这个过程才是Success
  • 啊,对。我不认为op 会失败,因为它使用map 而不是flatMap。顺便说一句,我认为.flatMap(_ => close) 可能是.orElse(close)
  • @Bergi write.orElse(close) 将是 Success(()) 如果 write 失败并且 close 成功(例如只读文件)。我希望只有当所有步骤都成功时操作才能成功。 (函数很棘手,不是吗?我在尝试回答时重写了很多次。我仍然不确定通过名称参数是否是传递流创建函数的最佳方式)
【解决方案2】:

在阅读了@maasg 的答案后,最终确定了该代码,该答案突出了单子流程并且更加“对称”。它看起来比 OP 中的代码好多了!

def safePrintToStream(gen: => OutputStream)(op: PrintWriter => Unit): Try[Unit] = {
  val os = Try(gen)
  val writer = os.map(stream => new PrintWriter(stream))
  val write = writer.map(op(_))
  val flush = writer.map(_.flush)
  val close = os.map(_.close)
  write.flatMap(_ => flush).flatMap(_ => close)
}

【讨论】:

  • 不应该 flushclose 在这段代码中偷懒吗?
猜你喜欢
  • 2011-11-17
  • 1970-01-01
  • 1970-01-01
  • 2014-11-27
  • 1970-01-01
  • 2011-10-31
  • 2013-12-16
  • 2011-11-17
  • 2018-10-24
相关资源
最近更新 更多