Scala中实现“使用/try-with-resources”(自动资源管理)的简单模式

20
C#使用usingIDisposable接口。Java 7+使用tryAutoCloseable接口实现相同的功能。Scala允许您选择自己的解决方案来处理此问题。
目前,scala-arm似乎是最受欢迎的选择,并由Typesafe公司的一名员工进行维护。然而,对于这么简单的行为来说,它似乎非常复杂。为了澄清,使用说明很简单,但理解所有该代码内部的工作方式则相当复杂。
我刚刚写了以下超级简单的ARM解决方案:
object SimpleARM {
  def apply[T, Q](c: T {def close(): Unit})(f: (T) => Q): Q = {
    try {
      f(c)
    } finally {
      c.close()
    }
  }
}
  • 像simple-arm这样的东西有什么好处吗?看起来所有额外的复杂性都应该带来额外的好处。
  • 通常,对于一般行为,使用由其他人支持的公共开源库要比使用自定义代码高度可取。
  • 有人能推荐任何改进吗?
  • 这种简单方法有什么限制吗?

据我所知,“c”的类型取决于反射,这可能在性能方面存在问题,并且在使用重构或字节码混淆时也会有问题。相反,我会在这里简单地重用类型java.lang.AutoCloseable。 - Christian Schlichtherle
你的代码没有处理 c == null 的情况。而且如果 close() 抛出异常,不清楚会抛出哪个异常。 - Dzmitry Lazerka
因为我需要能够嵌套多个java.lang.AutoCloseable实例,每个实例都依赖于先前的实例成功实例化,所以我最终想出了一个对我非常有用的模式。我将其写成了类似StackOverflow问题的答案:stackoverflow.com/a/34277491/501113 - chaotic3quilibrium
@chaotic3quilibrium,我下面的答案包含一个超级简单的Arm系统,支持你描述的嵌套类型。 - clay
糟糕。这是我的答案的可点击链接(关于类似和相关的问题):https://dev59.com/qG445IYBdhLWcg3w_O8m#34277491 - chaotic3quilibrium
9个回答

10

只要您不需要处理多个需要管理的资源,单一简单贷款模式的方法就可以正常工作。使用scala-arm单子方法可以实现这一点。

import resource.managed

managed(openResA).and(managed(openResB)) acquireFor { (a, b) => ??? }

val res = for {
  a <- managed(openResA)
  b <- managed(openResB)
  c <- managed(openResC)
} yield (a, b, c)

res acquireAndGet {
  case (a, b, c) => ???
}

Scala-arm 中需要知道的主要功能是 resource.managed.acquired{For,AndGet},顺便说一句,并不是很复杂。


3
ARM也在https://dev59.com/WGox5IYBdhLWcg3w9o6h中提到了这个项目。虽然该项目有所复苏,但它是否足够可靠以被包含在自己的项目中尚不清楚。该项目在"孵化器"状态下已经多年,而不是合并到RTL中... - Arioch 'The
有一个关于for推导式的重要bug:https://github.com/jsuereth/scala-arm/issues/49 - leventov

6

这是我最新的Scala ARM,简单易懂。它支持我能想到的所有用例,包括多个资源和yield值。使用非常简单的for推导式语法:

class AutoCloseableWrapper[A <: AutoCloseable](protected val c: A) {
  def map[B](f: (A) => B): B = {
    try {
      f(c)
    } finally {
      c.close()
    }
  }

  def foreach(f: (A) => Unit): Unit = map(f)

  // Not a proper flatMap.
  def flatMap[B](f: (A) => B): B = map(f)

  // Hack :)    
  def withFilter(f: (A) => Boolean) = this
}

object Arm {
  def apply[A <: AutoCloseable](c: A) = new AutoCloseableWrapper(c)
}

以下是示例用法:
class DemoCloseable(val s: String) extends AutoCloseable {
  var closed = false
  println(s"DemoCloseable create ${s}")

  override def close(): Unit = {
    println(s"DemoCloseable close ${s} previously closed=${closed}")
    closed = true
  }
}

object DemoCloseable {
  def unapply(dc: DemoCloseable): Option[(String)] = Some(dc.s)
}

object Demo {
  def main(args: Array[String]): Unit = {
    for (v <- Arm(new DemoCloseable("abc"))) {
      println(s"Using closeable ${v.s}")
    }

    for (a <- Arm(new DemoCloseable("a123"));
         b <- Arm(new DemoCloseable("b123"));
         c <- Arm(new DemoCloseable("c123"))) {
      println(s"Using multiple resources for comprehension. a.s=${a.s}. b.s=${b.s}. c.s=${c.s}")
    }

    val yieldInt = for (v <- Arm(new DemoCloseable("abc"))) yield 123
    println(s"yieldInt = $yieldInt")

    val yieldString = for (DemoCloseable(s) <- Arm(new DemoCloseable("abc")); c <- s) yield c
    println(s"yieldString = $yieldString")

    println("done")
  }
}

你的解决方案过于依赖OOP技巧,特别是AutoCloseableWrapper。我提供的解决方案更符合Scala函数式编程和不可变性的基本目标,同时保留了Java ARM模型的核心优势,而且只需一个函数和两个(柯里化)参数列表即可实现:https://dev59.com/qG445IYBdhLWcg3w_O8m#34277491 - chaotic3quilibrium

3

这是我使用的代码:

def use[A <: { def close(): Unit }, B](resource: A)(code: AB): B =
    try
        code(resource)
    finally
        resource.close()

与Java中的try-with-resources不同,资源无需实现AutoCloseable接口,只需要有一个close()方法即可。它仅支持一个资源。
以下是使用InputStream的示例:
val path = Paths get "/etc/myfile"
use(Files.newInputStream(path)) { inputStream ⇒
    val firstByte = inputStream.read()
    ....
}

1
你的解决方案隐式地依赖于反射。是的,鸭子类型,也就是你正在使用的类型,只能通过反射来实现。这意味着你的解决方案不能推广到像Scala.js或ScalaNative这样的地方。 - chaotic3quilibrium
我认为它不使用运行时反射。 - david.perez
这里还有更多关于结构类型的内容:https://dzone.com/articles/duck-typing-scala-structural - chaotic3quilibrium

1
这个对我非常有效:

this one works for me really well:

  implicit class ManagedCloseable[C <: AutoCloseable](resource: C) {
    def apply[T](block: (C) => T): T = {
    try {
      block(resource)
    } finally {
      resource.close()
    }
  }

例如,在这个Apache Cassandra客户端代码中使用它:

val metadata = Cluster.builder().addContactPoint("vader").withPort(1234).build() { cluster =>
  cluster.getMetadata
}

或者更短:

val metadata = Cluster.builder().addContactPoint("sedev01").withPort(9999).build()(_.getMetadata)

1

0

Choppy的懒惰TryClose单子可能是你正在寻找的(声明:我是作者)。它非常类似于Scala的Try,但会自动关闭资源。

val ds = new JdbcDataSource()
val output = for {
  conn  <- TryClose(ds.getConnection())
  ps    <- TryClose(conn.prepareStatement("select * from MyTable"))
  rs    <- TryClose.wrap(ps.executeQuery())
} yield wrap(extractResult(rs))

// Note that Nothing will actually be done until 'resolve' is called
output.resolve match {
    case Success(result) => // Do something
    case Failure(e) =>      // Handle Stuff
}

更多信息请参见此处:https://github.com/choppythelumberjack/tryclose


0
我可以推荐一种改进你提出的方法,即:
  def autoClose[A <: AutoCloseable, B](resource: A)(code: AB): B = {
    try
      code(resource)
    finally
      resource.close()
  }

使用的是:

  def autoClose[A <: AutoCloseable, B](resource: A)(code: AB): Try[B] = {
    val tryResult = Try {code(resource)}
    resource.close()
    tryResult
  }

在我看来,拥有一个Try[B]类型的tryResult将使您以后更容易控制流程。


0

以下是在Scala版本号<=2.12中实现此操作的一种方式。

trait SourceUtils {

  /**
    * Opens a `resource`, passes it to the given function `f`, then
    * closes the resource, returning the value returned from `f`.
    * 
    * Example usage:
    * {{{
    * val myString: String =
    *   using(scala.io.Source.fromFile("file.name")) { source =>
    *     source.getLines.mkString
    *   }
    * }}}
    * 
    * @param resource closeable resource to use, then close
    * @param f function which maps the resource to some return value
    * @tparam T type of the resource to be opened / closed
    * @tparam U type of the return value
    * @return the result of the function `f`
    */
  def using[T <: AutoCloseable, U](resource: => T)(f: T => U): U = {
    scala.util.Try(resource).fold(throw _, source => {
      val result = f(source)
      source.close()
      result
    })
  }
}

0

这是我会做的方式:

  def tryFinally[A, B](acquire: Try[A])(use: A => B)(release: A => Unit): Try[B] =
    for {
      resource <- acquire
      r = Try(use(resource)).fold(
        e => { release(resource); throw e },
        b => { release(resource); b }
      )
    } yield r


你可以调用.get方法,并使其返回B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接