Scala有哪些自动资源管理替代方案?


102

我在Web上看到了很多Scala的ARM(自动资源管理)示例。写一个似乎很容易,尽管大多数看上去很像。我确实看到了一个使用延续的非常酷的示例。

无论如何,很多代码都存在一种或另一种类型的缺陷,因此我认为在Stack Overflow上引用一个参考是一个好主意,我们可以在其中引用最正确和最合适的版本。


如果它不是社区Wiki,此问题会产生更多答案吗?请注意,如果在社区Wiki奖项声望中投票表决了答案……
huynhjl,2010年

2
唯一引用可以为ARM添加另一个安全级别,以确保在调用close()之前将对资源的引用返回给管理器。thread.gmane.org/gmane.comp.lang.scala/19160/focus=19168
返璞词

@retronym我认为唯一性插件将是一场革命,而不仅仅是延续。而且,实际上,我认为这是Scala中的一件事,很可能会在不久的将来将其移植到其他语言中。当出现这种情况时,请确保相应地编辑答案。:-)
Daniel C. Sobral

1
因为我需要能够嵌套多个java.lang.AutoCloseable实例,每个实例都取决于成功实例化的前一个实例,所以我最终找到了一种对我非常有用的模式。我把它写成是对类似StackOverflow问题的回答:stackoverflow.com/a/34277491/501113
chaotic3quilibrium,2016年

Answers:


10

目前,Scala 2.13最终支持:try with resources通过使用Using :),例如:

val lines: Try[Seq[String]] =
  Using(new BufferedReader(new FileReader("file.txt"))) { reader =>
    Iterator.unfold(())(_ => Option(reader.readLine()).map(_ -> ())).toList
  }

或使用Using.resource避免Try

val lines: Seq[String] =
  Using.resource(new BufferedReader(new FileReader("file.txt"))) { reader =>
    Iterator.unfold(())(_ => Option(reader.readLine()).map(_ -> ())).toList
  }

您可以从使用文档中找到更多示例。

用于执行自动资源管理的实用程序。它可以用来执行使用资源的操作,然后以创建资源的相反顺序释放资源。


您能否同时添加该Using.resource变体?
Daniel C. Sobral

@ DanielC.Sobral,当然,刚刚添加了它。
chengpohi

您将如何为Scala 2.12编写此代码?这是一种类似的using方法:def using[A <: AutoCloseable, B](resource: A) (block: A => B): B = try block(resource) finally resource.close()
Mike Slinn

75

克里斯·汉森(Chris Hansen)在2009年3月26日发表博客文章“斯卡拉的ARM块:再访”中谈到了马丁· 奥德斯基(Martin Odersky)的FOSDEM演示文稿的幻灯片21 。下一个步骤直接摘自幻灯片21(经许可):

def using[T <: { def close() }]
    (resource: T)
    (block: T => Unit) 
{
  try {
    block(resource)
  } finally {
    if (resource != null) resource.close()
  }
}

-报价-

然后我们可以这样调用:

using(new BufferedReader(new FileReader("file"))) { r =>
  var count = 0
  while (r.readLine != null) count += 1
  println(count)
}

这种方法的缺点是什么?这种模式似乎可以解决我需要自动资源管理的95%...

编辑:添加了代码段


Edit2:扩展设计模式-从python with语句中获取灵感并解决:

  • 语句在块之前运行
  • 重新抛出异常取决于托管资源
  • 使用一个using语句处理两个资源
  • 通过提供隐式转换和Managed类来进行资源特定的处理

这是在Scala 2.8中。

trait Managed[T] {
  def onEnter(): T
  def onExit(t:Throwable = null): Unit
  def attempt(block: => Unit): Unit = {
    try { block } finally {}
  }
}

def using[T <: Any](managed: Managed[T])(block: T => Unit) {
  val resource = managed.onEnter()
  var exception = false
  try { block(resource) } catch  {
    case t:Throwable => exception = true; managed.onExit(t)
  } finally {
    if (!exception) managed.onExit()
  }
}

def using[T <: Any, U <: Any]
    (managed1: Managed[T], managed2: Managed[U])
    (block: T => U => Unit) {
  using[T](managed1) { r =>
    using[U](managed2) { s => block(r)(s) }
  }
}

class ManagedOS(out:OutputStream) extends Managed[OutputStream] {
  def onEnter(): OutputStream = out
  def onExit(t:Throwable = null): Unit = {
    attempt(out.close())
    if (t != null) throw t
  }
}
class ManagedIS(in:InputStream) extends Managed[InputStream] {
  def onEnter(): InputStream = in
  def onExit(t:Throwable = null): Unit = {
    attempt(in.close())
    if (t != null) throw t
  }
}

implicit def os2managed(out:OutputStream): Managed[OutputStream] = {
  return new ManagedOS(out)
}
implicit def is2managed(in:InputStream): Managed[InputStream] = {
  return new ManagedIS(in)
}

def main(args:Array[String]): Unit = {
  using(new FileInputStream("foo.txt"), new FileOutputStream("bar.txt")) { 
    in => out =>
    Iterator continually { in.read() } takeWhile( _ != -1) foreach { 
      out.write(_) 
    }
  }
}

2
有其他选择,但我并不是要暗示这有问题。我只想在Stack Overflow上获得所有这些答案。:-)
Daniel C. Sobral

5
您知道标准API中是否存在类似内容?似乎总是需要自己为自己编写此书的琐事。
Daniel Darabos 2014年

自发布以来已经有一段时间了,但是如果out构造函数抛出异常,第一个解决方案不会关闭内部流,这可能不会在这里发生,但是在其他情况下,这可能很糟糕。收盘也可以抛出。致命异常之间也没有区别。第二个代码无处不在,并且比第一个具有零优势。您甚至松开实际的类型,因此对于ZipInputStream之类的东西将毫无用处。
steinybot '16

如果块返回迭代器,您如何建议这样做?
豪尔赫·马查多

62

丹尼尔

我最近刚刚部署了scala-arm库来进行自动资源管理。您可以在这里找到文档:https : //github.com/jsuereth/scala-arm/wiki

该库支持三种使用方式(当前):

1)命令式/表达式:

import resource._
for(input <- managed(new FileInputStream("test.txt")) {
// Code that uses the input as a FileInputStream
}

2)单子风格

import resource._
import java.io._
val lines = for { input <- managed(new FileInputStream("test.txt"))
                  val bufferedReader = new BufferedReader(new InputStreamReader(input)) 
                  line <- makeBufferedReaderLineIterator(bufferedReader)
                } yield line.trim()
lines foreach println

3)分隔延续样式

这是一个“ echo” tcp服务器:

import java.io._
import util.continuations._
import resource._
def each_line_from(r : BufferedReader) : String @suspendable =
  shift { k =>
    var line = r.readLine
    while(line != null) {
      k(line)
      line = r.readLine
    }
  }
reset {
  val server = managed(new ServerSocket(8007)) !
  while(true) {
    // This reset is not needed, however the  below denotes a "flow" of execution that can be deferred.
    // One can envision an asynchronous execuction model that would support the exact same semantics as below.
    reset {
      val connection = managed(server.accept) !
      val output = managed(connection.getOutputStream) !
      val input = managed(connection.getInputStream) !
      val writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(output)))
      val reader = new BufferedReader(new InputStreamReader(input))
      writer.println(each_line_from(reader))
      writer.flush()
    }
  }
}

该代码利用了资源类型特征,因此能够适应大多数资源类型。使用带有close或dispose方法的类对类使用结构化类型具有后备功能。请查阅文档,如果您想添加任何方便的功能,请告诉我。


1
是的,我看到了。我想看一下代码,看看如何完成某些事情,但是我现在太忙了。无论如何,由于问题的目的是提供对可靠的ARM代码的引用,因此我将其作为公认的答案。
Daniel C. Sobral

18

这是使用延续的James Iry解决方案:

// standard using block definition
def using[X <: {def close()}, A](resource : X)(f : X => A) = {
   try {
     f(resource)
   } finally {
     resource.close()
   }
}

// A DC version of 'using' 
def resource[X <: {def close()}, B](res : X) = shift(using[X, B](res))

// some sugar for reset
def withResources[A, C](x : => A @cps[A, C]) = reset{x}

以下是有或没有继续进行比较的解决方案:

def copyFileCPS = using(new BufferedReader(new FileReader("test.txt"))) {
  reader => {
   using(new BufferedWriter(new FileWriter("test_copy.txt"))) {
      writer => {
        var line = reader.readLine
        var count = 0
        while (line != null) {
          count += 1
          writer.write(line)
          writer.newLine
          line = reader.readLine
        }
        count
      }
    }
  }
}

def copyFileDC = withResources {
  val reader = resource[BufferedReader,Int](new BufferedReader(new FileReader("test.txt")))
  val writer = resource[BufferedWriter,Int](new BufferedWriter(new FileWriter("test_copy.txt")))
  var line = reader.readLine
  var count = 0
  while(line != null) {
    count += 1
    writer write line
    writer.newLine
    line = reader.readLine
  }
  count
}

这是Tiark Rompf的改进建议:

trait ContextType[B]
def forceContextType[B]: ContextType[B] = null

// A DC version of 'using'
def resource[X <: {def close()}, B: ContextType](res : X): X @cps[B,B] = shift(using[X, B](res))

// some sugar for reset
def withResources[A](x : => A @cps[A, A]) = reset{x}

// and now use our new lib
def copyFileDC = withResources {
 implicit val _ = forceContextType[Int]
 val reader = resource(new BufferedReader(new FileReader("test.txt")))
 val writer = resource(new BufferedWriter(new FileWriter("test_copy.txt")))
 var line = reader.readLine
 var count = 0
 while(line != null) {
   count += 1
   writer write line
   writer.newLine
   line = reader.readLine
 }
 count
}

当BufferedWriter构造函数失败时,不使用(new BufferedWriter(new FileWriter(“ test_copy.txt”)))会遇到问题吗?每个资源都应包装在using块中……
Jaap 2013年

@Jaap这是Oracle建议的样式。BufferedWriter不会引发检查的异常,因此如果引发任何异常,则程序无法从中恢复。
Daniel C. Sobral

7

我看到在Scala中进行ARM的4步逐步演进:

  1. 没有ARM:土
  2. 仅闭包:更好,但有多个嵌套块
  3. Continuation Monad:用于使拼合变平,但不自然地分成2个块
  4. 直接样式延续:Nirava,啊哈!这也是最安全的类型选择:withResource块之外的资源将是类型错误。

1
请注意,Scala中的CPS是通过monad实现的。:-)
Daniel C. Sobral

1
Mushtaq,3)您可以在不是连续单子的monad中进行资源管理。4)使用withwithResources /资源分隔的连续代码的资源管理比“使用”安全得多(且不少于)。仍然有可能忘记管理需要它的资源。比较using(new Resource()){first => val second = new Resource()//糟糕!// //使用资源} //只有firstResources关闭{val first = resource(new Resource())val second = new Resource()//糟糕!//使用资源...} //只有先关闭
James Iry

2
Daniel,Scala中的CPS就像任何功能语言中的CPS一样。它是使用monad分隔的延续。
James Iry

詹姆斯,谢谢您的解释。坐在印度,我只希望我能参加您的BASE演讲。等待看看何时将这些幻灯片放到网上:)
Mushtaq Ahmed

6

更好的文件包含轻量级(10行代码)ARM。参见:https : //github.com/pathikrit/better-files#lightweight-arm

import better.files._
for {
  in <- inputStream.autoClosed
  out <- outputStream.autoClosed
} in.pipeTo(out)
// The input and output streams are auto-closed once out of scope

如果您不希望使用整个库,可以通过以下方式实现:

  type Closeable = {
    def close(): Unit
  }

  type ManagedResource[A <: Closeable] = Traversable[A]

  implicit class CloseableOps[A <: Closeable](resource: A) {        
    def autoClosed: ManagedResource[A] = new Traversable[A] {
      override def foreach[U](f: A => U) = try {
        f(resource)
      } finally {
        resource.close()
      }
    }
  }

很好 我采取了与该方法类似的方法,但为CloseableOps 定义了mapand flatMap方法,而不是foreach,以便进行理解时不会产生遍历。
EdgeCaseBerg '17

1

如何使用Type类

trait GenericDisposable[-T] {
   def dispose(v:T):Unit
}
...

def using[T,U](r:T)(block:T => U)(implicit disp:GenericDisposable[T]):U = try {
   block(r)
} finally { 
   Option(r).foreach { r => disp.dispose(r) } 
}

1

另一种选择是Choppy的Lazy TryClose monad。与数据库连接非常好:

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
}

和流:

val output = for {
  outputStream      <- TryClose(new ByteArrayOutputStream())
  gzipOutputStream  <- TryClose(new GZIPOutputStream(outputStream))
  _                 <- TryClose.wrap(gzipOutputStream.write(content))
} yield wrap({gzipOutputStream.flush(); outputStream.toByteArray})

output.resolve.unwrap match {
  case Success(bytes) => // process result
  case Failure(e) => // handle exception
}

更多信息在这里:https : //github.com/choppythelumberjack/tryclose


0

这是@chengpohi的答案,已修改,因此它可以与Scala 2.8+一起使用,而不仅仅是Scala 2.13(是的,它也可以与Scala 2.13一起使用):

def unfold[A, S](start: S)(op: S => Option[(A, S)]): List[A] =
  Iterator
    .iterate(op(start))(_.flatMap{ case (_, s) => op(s) })
    .map(_.map(_._1))
    .takeWhile(_.isDefined)
    .flatten
    .toList

def using[A <: AutoCloseable, B](resource: A)
                                (block: A => B): B =
  try block(resource) finally resource.close()

val lines: Seq[String] =
  using(new BufferedReader(new FileReader("file.txt"))) { reader =>
    unfold(())(_ => Option(reader.readLine()).map(_ -> ())).toList
  }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.