【问题标题】:How to ensure a resource is closed in a for-comprehension in Scala如何确保资源在 Scala 中以易于理解的方式关闭
【发布时间】:2015-11-27 20:07:09
【问题描述】:

如何在 Scala 的 for-comprehensions 中最好地处理具有副作用的函数?

我有一个 for 理解,它首先通过调用函数 f1 创建一种资源 (x)。该资源有一个 close 方法,需要在最后调用,但如果理解以某种方式失败(除非。

所以我们有类似的东西:

import scala.util.{Try,Success,Failure}

trait Resource {
  def close() : Unit
}

// Opens some resource and returns it as Success or returns Failure
def f1 : Try[Resource] = ...
def f2 : Try[Resource] = ...

val res = for {
  x <- f1
  y <- f2
} yield {
  (x,y)
}

我应该在哪里调用 close 方法?我可以在 for-comprehension 的末尾将其称为最后一条语句 (z f2 失败),它们都不能确保调用 close。 或者,我可以分开

x <- f1 

不理解是这样的:

val res = f1
res match {
  case Success(x) => {
    for {
      y <- f2
    }
    x.close
  }

  case Failure(e) => ...       
:

这将确保调用 close 但不是很好的代码。 难道没有更聪明、更干净的方法来实现同样的目标吗?

【问题讨论】:

标签: scala


【解决方案1】:

当我遇到这样的问题时,我会在两种可能性之间做出选择:

  1. 使用Scala ARM
  2. 自行实现Loan Pattern(链接不稳定,可能会失效)

在大多数情况下,我更喜欢自己的实现以避免额外的依赖。 这是贷款模式的代码:

def using[A](r : Resource)(f : Resource => A) : A =
    try {
        f(r)
    } finally {
        r.close()
    }

用法:

using(getResource())(r =>
    useResource(r)
)

由于您需要 2 个资源,因此您需要使用此模式两次:

using(getResource1())(r1 =>
    using(getResource2())(r2 =>
        doYourWork(r1, r2)))

您还可以查看以下答案:

【讨论】:

    【解决方案2】:

    关闭资源的常见模式是贷款模式:

    type Closable = { def close(): Unit }
    
    def withClosable[B](closable: Closable)(op: Closable => B): B = {
      try {
        op(closable)
      } finally {
        closable.close()
      }
    }
    

    稍作重构,您就可以使用这种模式:

    import scala.util.{Try,Success,Failure}
    
    trait Resource {
      def close() : Unit
    }
    
    // Opens some resource and returns it as Success or returns Failure
    def f1(res: Resource) : Try[Resource] = ???
    def f2(res: Resource) : Try[Resource] = ???
    
    val f1Resource: Resource = ???
    val f2Resource: Resource = ???
    
    val res = for {
      x <- withClosable(f1Resource)(f1)
      y <- withClosable(f2Resource)(f2)
    } yield {
      (x,y)
    }
    

    import scala.util.{Try,Success,Failure}
    
    trait Resource {
      def close() : Unit
    }
    
    // Opens some resource and returns it as Success or returns Failure
    def f1: Try[Resource] = { 
      val res: Resource = ???
      withClosable(res){ ... }
    }
    def f2: Try[Resource] = { 
      val res: Resource = ???
      withClosable(res){ ... }
    }
    
    val res = for {
      x <- f1
      y <- f2
    } yield {
      (x,y)
    }
    

    【讨论】:

      【解决方案3】:

      你可以使用

      https://github.com/jsuereth/scala-arm

      如果你的“资源”没有实现 java.io.Closeable(或其他一些可关闭的接口,比库支持),你只需要编写一个隐式转换:

      implicit def yourEnititySupport[A <: your.closable.Enitity]: Resource[A] = 
        new Resource[A] {
          override def close(r: A) = r.commit()
          // if you need custom behavior here
          override def closeAfterException(r: A, t: Throwable) = r.rollback()
        }
      

      并像这样使用它:

      import resource._
      
      for {
        a <- managed(your.closable.Enitity())
        b <- managed(your.closable.Enitity())
      } { doSomething(a, b) }
      

      【讨论】:

      • 在我的情况下,重要的是资源保持打开,同时执行理解中的所有步骤,并且直到最后一个成功或一个步骤失败才关闭。对我来说,上面的建议似乎在每一步之后都会关闭资源。它应该类似于: * 打开资源 * 如果成功,则在使用资源的地方执行理解。 * 如果打开资源失败什么也不做。 * 理解后:关闭资源。如果理解失败,也关闭。我希望这能解释它。
      • “每一步”是什么意思?建议的代码在执行整个块 { doSomething(a, b) } 后关闭托管资源。
      猜你喜欢
      • 2012-02-03
      • 1970-01-01
      • 2017-06-21
      • 2011-08-12
      • 1970-01-01
      • 2019-09-26
      • 1970-01-01
      • 2023-04-02
      • 1970-01-01
      相关资源
      最近更新 更多