Scala 都有哪些自动资源管理替代方案?
Posted
技术标签:
【中文标题】Scala 都有哪些自动资源管理替代方案?【英文标题】:What Automatic Resource Management alternatives exist for Scala?Scala 有哪些自动资源管理替代方案? 【发布时间】:2011-01-13 12:37:55 【问题描述】:我在网上看到了很多 Scala 的 ARM(自动资源管理)示例。写一个似乎是一种仪式,尽管大多数看起来很像。不过,我确实看到了一个使用延续的非常酷的示例。
无论如何,很多代码都有某种类型的缺陷,所以我认为在 Stack Overflow 上有一个参考是一个好主意,我们可以在这里投票选出最正确和最合适的版本。
【问题讨论】:
如果不是社区 wiki,这个问题会产生更多答案吗?请注意是否在社区 wiki 奖励声誉中投票答案... 唯一引用可以为 ARM 添加另一个安全级别,以确保在调用 close() 之前将资源引用返回给管理器。 thread.gmane.org/gmane.comp.lang.scala/19160/focus=19168 @retronym 我认为唯一性插件将是一场革命,而不是延续。而且,事实上,我认为这是 Scala 中的一件事,它很可能会在不久的将来被移植到其他语言。当它出现时,让我们确保相应地编辑答案。 :-) 因为我需要能够嵌套多个 java.lang.AutoCloseable 实例,每个实例都依赖于前一个成功实例化的实例,所以我终于找到了一个对我非常有用的模式。我把它写成类似 *** 问题的答案:***.com/a/34277491/501113 【参考方案1】:Chris Hansen 的 blog entry 'ARM Blocks in Scala: Revisited' from 3/26/09 谈到了 Martin Odersky 的 FOSDEM presentation 的幻灯片 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%...
编辑:添加代码sn-p
Edit2:扩展设计模式 - 从 python with
语句和寻址中获取灵感:
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(_)
【讨论】:
还有其他选择,但我并不是要暗示这有什么问题。我只想在 Stack Overflow 上找到所有这些答案。 :-) 你知道标准API中是否有这样的东西吗?一直为自己写这个似乎是件苦差事。 自此发布以来已经有一段时间了,但是如果 out 构造函数抛出,第一个解决方案不会关闭内部流,这可能不会在这里发生,但在其他情况下这可能会很糟糕。 close 也可以抛出。致命异常之间也没有区别。第二个到处都有代码气味,比第一个优势为零。您甚至会丢失实际类型,因此对于 ZipInputStream 之类的东西将毫无用处。 如果块返回迭代器,您建议如何执行此操作?【参考方案2】:丹尼尔,
我最近刚刚部署了用于自动资源管理的 scala-arm 库。你可以在这里找到文档:https://github.com/jsuereth/scala-arm/wiki
这个库支持三种使用方式(目前):
1) 命令式/for-表达式:
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 方法对类使用结构类型。请查看文档,如果您想添加任何方便的功能,请告诉我。
【讨论】:
是的,我看到了。我想看一下代码,看看你是如何完成一些事情的,但我现在太忙了。无论如何,由于问题的目标是提供对可靠 ARM 代码的参考,因此我将其作为公认的答案。【参考方案3】:这是使用延续的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]) = resetx
以下是用于比较的带有和不带有延续的解决方案:
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]) = resetx
// 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
【讨论】:
Doesn't using(new BufferedWriter(new FileWriter("test_copy.txt"))) 在 BufferedWriter 构造函数失败时遇到问题?每个资源都应该包装在 using 块中... @Jaap 这是suggested by Oracle 的风格。BufferedWriter
不会抛出已检查的异常,因此如果抛出任何异常,程序预计不会从中恢复。【参考方案4】:
目前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 doc 中找到更多示例。
用于执行自动资源管理的实用程序。它可用于执行使用资源的操作,然后以与创建资源相反的顺序释放资源。
【讨论】:
能否也添加Using.resource
变体?
@DanielC.Sobral,当然,刚刚添加。
你会如何为 Scala 2.12 编写这个?这是一个类似的using
方法:def using[A <: AutoCloseable, B](resource: A) (block: A => B): B = try block(resource) finally resource.close()
【参考方案5】:
我看到了在 Scala 中进行 ARM 的逐步演进 4 步:
-
没有 ARM:污垢
仅闭包:更好,但有多个嵌套块
Continuation Monad:使用 For 将嵌套变平,但不自然地分隔为 2 个块
直接风格延续:Nirava,啊哈!这也是最类型安全的替代方案:withResource 块之外的资源将出现类型错误。
【讨论】:
请注意,Scala 中的 CPS 是通过 monad 实现的。 :-) Mushtaq, 3) 您可以在不是 continuation 的 monad 的 monad 中进行资源管理 4) 使用我的 withResources/resource 分隔的 continuations 代码进行资源管理不再是(也不少于)类型安全而不是“使用”。仍然可能忘记管理需要它的资源。比较 using(new Resource()) first => val second = new Resource() //哎呀! // 使用资源 // 只有第一次被关闭 withResources val first = resource(new Resource()) val second = new Resource() // 哎呀! // 使用资源... // 只有第一次被关闭 Daniel,Scala 中的 CPS 就像任何函数式语言中的 CPS。它是使用 monad 的分隔延续。 詹姆斯,谢谢你的解释。坐在印度,我只希望我在那儿听你的 BASE 演讲。等着看你什么时候把这些幻灯片放到网上:)【参考方案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 而不是 foreach 定义了map
和 flatMap
方法,以便理解不会产生可遍历。【参考方案7】:
如何使用 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)
【讨论】:
【参考方案8】:另一种选择是 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
【讨论】:
【参考方案9】:这是@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
【讨论】:
【参考方案10】:虽然Using
可以,但我更喜欢单子风格的资源组合。 Twitter Util 的 Managed
非常好,除了它的依赖项和不太完善的 API。
为此,我为 Scala 2.12、2.13 和 3.0.0 发布了 https://github.com/dvgica/managerial。很大程度上基于 Twitter Util Managed
代码,没有依赖关系,一些 API 改进受到猫效应 Resource
的启发。
简单的例子:
import ca.dvgi.managerial._
val fileContents = Managed.from(scala.io.Source.fromFile("file.txt")).use(_.mkString)
但图书馆的真正实力是composing resources via for comprehensions。
让我知道你的想法!
【讨论】:
以上是关于Scala 都有哪些自动资源管理替代方案?的主要内容,如果未能解决你的问题,请参考以下文章
Visual Studio 的循环矢量化(手动和自动)都有哪些资源?
目前流行的源程序版本管理软件和项目管理软件都有哪些, 各有什么优缺点?