用于“使用/尝试使用资源”的简单 Scala 模式(自动资源管理)

Posted

技术标签:

【中文标题】用于“使用/尝试使用资源”的简单 Scala 模式(自动资源管理)【英文标题】:Simple Scala pattern for "using/try-with-resources" (Automatic Resource Management) 【发布时间】:2014-10-27 08:55:28 【问题描述】:

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

像单臂这样的东西有什么好处吗?似乎所有额外的复杂性都应该带来额外的好处。 通常,与使用自定义代码相比,最好使用其他人支持的公共、开源库来实现通用行为。 谁能推荐任何改进? 这种简单的方法有什么限制吗?

【问题讨论】:

AFAIK,“c”的类型取决于反射,这在性能和使用重构或字节码混淆时可能会出现问题。相反,我会在这里简单地重用 java.lang.AutoCloseable 类型。 您的代码无法处理 c == null 情况。如果 close() 也抛出异常,也不清楚会抛出哪个异常。 因为我需要能够嵌套多个 java.lang.AutoCloseable 实例,每个实例都依赖于前一个成功实例化的实例,所以我终于找到了一个对我非常有用的模式。我把它写成类似 *** 问题的答案:***.com/a/34277491/501113 @chaotic3quilibrium,我下面的答案包含一个超级简单的 Arm 系统,支持您描述的嵌套类型。 糟糕。这是我的答案的可点击链接(关于类似和相关的问题):***.com/a/34277491/501113 【参考方案1】:

只要您不需要使用多个资源,所有资源都需要管理,您使用单一简单贷款模式的方法就可以正常工作。这在 scala-arm monadic 方法中是允许的。

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.acquiredFor,AndGet,顺便说一句,并不是很复杂。

【讨论】:

ARM 在***.com/questions/8865754 上也提到过,但是虽然该项目有些振兴,但它是否足够可靠以包含在自己的项目中尚不清楚。这么多年处于“孵化器”状态而不是合并到 RTL... 有一个关于理解的重要错误:github.com/jsuereth/scala-arm/issues/49【参考方案2】:

这里是我较新的简单,一目了然,Scala ARM。这完全支持我能想到的每个用例,包括多个资源和收益值。这使用了一个非常简单的理解用法语法:

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 hack,特别是 AutoCloseableWrapper。我提供的解决方案更符合 Scala 的 FP 和不变性的基本目标,同时保留了 Java ARM 模型的核心优势,所有这些都在一个带有两个(currying)参数列表的函数中:***.com/a/34277491/501113【参考方案3】:

这是我使用的代码:

def use[A <:  def close(): Unit , B](resource: A)(code: A ⇒ B): 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()
    ....

【讨论】:

您的解决方案隐含地依赖于反射。是的,您使用的鸭子类型只能通过反射获得。因此,这意味着您的解决方案不能推广到 Scala.js 或 ScalaNative 等地方。 我认为它不使用运行时反射。 还有更多关于结构类型的内容:dzone.com/articles/duck-typing-scala-structural【参考方案4】:

http://illegalexception.schlichtherle.de/2012/07/19/try-with-resources-for-scala/

另一个实现,从“遵循 Java 规范”的角度来看可能更干净,但也不支持多种资源

【讨论】:

【参考方案5】:

这个非常适合我:

  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)

【讨论】:

【参考方案6】:

我可以对您建议的方法提出改进建议,即:

  def autoClose[A <: AutoCloseable, B](resource: A)(code: A ⇒ B): B = 
    try
      code(resource)
    finally
      resource.close()
  

正在使用:

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

恕我直言,拥有 Try[B] 的 tryResult 将允许您稍后更轻松地控制流程。

【讨论】:

【参考方案7】:

Choppy 的 Lazy TryClose monad 可能是您正在寻找的(披露:我是作者)。它与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

【讨论】:

【参考方案8】:

这就是我的做法:

  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

【讨论】:

【参考方案9】:

这是在 Scala 中执行此操作的一种方法

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
    )
  

【讨论】:

以上是关于用于“使用/尝试使用资源”的简单 Scala 模式(自动资源管理)的主要内容,如果未能解决你的问题,请参考以下文章

大数据spark学习第一周Scala语言基础

如何在 Scala 中使用 switch/case(简单模式匹配)?

Scala 模式匹配

2021年大数据常用语言Scala(三十三):scala高级用法 模式匹配

Scala 学习 -- 样例类和模式匹配

Scala 学习 -- 样例类和模式匹配