我在网上看到很多关于Scala中ARM(自动资源管理)的例子。似乎编写一个这样的例子是成长过程中必经的,但大多数看起来几乎都一样。不过,我确实看到了一个使用continuations的相当酷的例子。
无论如何,许多代码都存在某种类型的缺陷,因此我认为在Stack Overflow上有一个参考是个好主意,我们可以投票支持最正确和适当的版本。
我在网上看到很多关于Scala中ARM(自动资源管理)的例子。似乎编写一个这样的例子是成长过程中必经的,但大多数看起来几乎都一样。不过,我确实看到了一个使用continuations的相当酷的例子。
无论如何,许多代码都存在某种类型的缺陷,因此我认为在Stack Overflow上有一个参考是个好主意,我们可以投票支持最正确和适当的版本。
Chris Hansen在他于2009年3月26日发布的博客文章“Scala中的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)
}
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(_)
}
}
}
def using[R, T <: {def close(): Unit}](resource: T)(fnc: T => R): R = { ... }
- surfealokeseadef using[R, T <: {def close(): Unit}](resource: T)(fnc: T => R): R = { ... }
- undefined我最近刚刚部署了scala-arm库,用于自动资源管理。你可以在这里找到文档:https://github.com/jsuereth/scala-arm/wiki
目前,该库支持三种使用方式:
命令式/for表达式:
import resource._
for(input <- managed(new FileInputStream("test.txt"))) {
// 使用input作为FileInputStream的代码
}
单子风格
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
分界限延续风格
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()
}
}
}
以下是James Iry使用continuations的解决方案:
// 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}
以下是带有continuations和不带continuations的解决方案供比较:
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
}
目前,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文档中找到更多示例。
一个用于执行自动资源管理的工具。它可用于使用资源执行操作,执行完成后按照创建的相反顺序释放资源。
Using.resource
变体好吗? - Daniel C. Sobralusing
方法: (block: A => B): B =
try block(resource) finally resource.close()```
- Mike Slinn我认为使用Scala进行ARM的演变可以分为4个步骤:
using(new Resource()) { first =>
val second = new Resource() //oops!
// use resources
} // only first gets closed
withResources {
val first = resource(new Resource())
val second = new Resource() // oops!
// use resources...
} // only first gets closed
- James Iry在better-files中包含了轻量级(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()
}
}
}
map
和flatMap
方法,而不是使用foreach,以便for推导不会产生可遍历对象。 - EdgeCaseBerg另一种选择是Choppy的懒惰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
}
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) }
}
这里是@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
}
虽然使用Using
也可以,但我更喜欢资源组合的单子样式。Twitter Util的Managed
非常好,除了它的依赖和不太光滑的API。
为此,我已经发布了https://github.com/dvgica/managerial,适用于Scala 2.12、2.13和3.0.0。主要基于Twitter Util Managed
代码,无依赖项,一些API改进受到cats-effect Resource
的启发。
下面是一个简单的例子:
import ca.dvgi.managerial._
val fileContents = Managed.from(scala.io.Source.fromFile("file.txt")).use(_.mkString)
但是该库的真正强大之处在于通过for comprehensions组合资源。
让我知道你的想法!