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

Dan*_*ral 100 scala resource-management

我在Web上看到了许多针对Scala的ARM(自动资源管理)示例.虽然大多数看起来很像彼此,但它似乎是一种写作的通道.不过,我确实看到了一个使用延续的非常酷的例子.

无论如何,很多代码都有这种或那种类型的缺陷,所以我认为在Stack Overflow上有一个引用是个好主意,我们可以在这里投票给出最正确和最合适的版本.

huy*_*hjl 74

Chris Hansen的博客文章"ARM Scala:Revisited"于2009年3月26日讨论了关于Martin Odersky的FOSDEM演示文稿的幻灯片21 .下一个块直接从幻灯片21(获得许可)获取:

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

- 结束报价 -

然后我们可以像这样打电话:

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

这种方法的缺点是什么?这种模式似乎可以解决我需要自动资源管理的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(_) 
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 你知道标准API中是否有这样的东西吗?看起来像是一件苦差事,必须一直为自己写这个. (5认同)
  • 有其他选择,但我并不是要暗示这有什么不妥.我只想在Stack Overflow上找到所有这些答案.:-) (2认同)

jsu*_*eth 62

丹尼尔,

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

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

1)势在必行/表达:

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

2)Monadic风格

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
Run Code Online (Sandbox Code Playgroud)

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()
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

该代码使用Resource类型特征,因此它能够适应大多数资源类型.对于使用close或dispose方法的类使用结构类型,它有一个后备.如果您想要添加任何便利功能,请查看文档并告诉我们.


Dan*_*ral 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}
Run Code Online (Sandbox Code Playgroud)

以下是有和没有继续比较的解决方案:

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
}
Run Code Online (Sandbox Code Playgroud)

这是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
}
Run Code Online (Sandbox Code Playgroud)


Mus*_*med 7

我看到在Scala中执行ARM的渐进式4步演变:

  1. 没有ARM:污垢
  2. 只有闭包:更好,但多个嵌套块
  3. Continuation Monad:用于平整嵌套,但在2个区块中不自然分离
  4. 直接风格延续:Nirava,啊哈!这也是最类型安全的替代方法:withResource块外部的资源将是类型错误.

  • 丹尼尔,Scala中的CPS就像任何功能语言中的CPS一样.它是使用monad的分隔延续. (2认同)

pat*_*rit 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
Run Code Online (Sandbox Code Playgroud)

如果您不想要整个库,这是如何实现的:

  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()
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)


che*_*ohi 6

目前,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
  }
Run Code Online (Sandbox Code Playgroud)

或使用Using.resource避免Try

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

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

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