我已将'使用'功能定义如下:
def using[A, B <: {def close(): Unit}] (closeable: B) (f: B => A): A =
try { f(closeable) } finally { closeable.close() }
我可以这样使用它:
using(new PrintWriter("sample.txt")){ out =>
out.println("hellow world!")
}
现在我很好奇如何定义'使用'功能来获取任意数量的参数,并且能够单独访问它们:
using(new BufferedReader(new FileReader("in.txt")), new PrintWriter("out.txt")){ (in, out) =>
out.println(in.readLIne)
}
答案 0 :(得分:11)
有人已经这样做了 - 它被称为Scala ARM。
自述文件:
import resource._
for(input <- managed(new FileInputStream("test.txt")) {
// Code that uses the input as a FileInputStream
}
答案 1 :(得分:6)
我一直在考虑这个问题,我想可能还有其他方法可以解决这个问题。以下是我对支持“任意数量”参数的看法(受元组提供的限制):
object UsingTest {
type Closeable = {def close():Unit }
final class CloseAfter[A<:Product](val x: A) {
def closeAfter[B](block: A=>B): B = {
try {
block(x);
} finally {
for (i <- 0 until x.productArity) {
x.productElement(i) match {
case c:Closeable => println("closing " + c); c.close()
case _ =>
}
}
}
}
}
implicit def any2CloseAfter[A<:Product](x: A): CloseAfter[A] =
new CloseAfter(x)
def main(args:Array[String]): Unit = {
import java.io._
(new BufferedReader(new FileReader("in.txt")),
new PrintWriter("out.txt"),
new PrintWriter("sample.txt")) closeAfter {case (in, out, other) =>
out.println(in.readLine)
other.println("hello world!")
}
}
}
我认为我正在重复使用库中已编写22个元组/产品类的事实......我认为这种语法不比使用嵌套using
更清晰(没有双关语),但这是一个有趣的难题。
编辑:按照反义词的建议用case (in, out, other)
替换了val赋值。
答案 2 :(得分:2)
不幸的是,标准Scala中不支持任意类型的任意长度参数列表。
您可以通过几种语言更改来执行此类操作(允许将变量参数列表作为HLists传递;请参阅here了解所需内容的约1/3)。
现在,最好的办法就是做Tuple和Function所做的事情:根据需要使用N实现N个。
当然,两个很容易:
def using2[A, B <: {def close(): Unit}, C <: { def close(): Unit}](closeB: B, closeC: C)(f: (B,C) => A): A = {
try { f(closeB,closeC) } finally { closeB.close(); closeC.close() }
}
如果你需要更多,那么写一些会生成源代码的东西可能是值得的。
答案 3 :(得分:2)
这是一个示例,允许您将scala用于理解,作为任何java.io.Closeable项的自动资源管理块,但可以使用close方法轻松扩展它以适用于任何对象。
这种用法似乎与using语句非常接近,允许您轻松地在一个块中定义尽可能多的资源。
object ResourceTest{
import CloseableResource._
import java.io._
def test(){
for( input <- new BufferedReader(new FileReader("/tmp/input.txt")); output <- new FileWriter("/tmp/output.txt") ){
output.write(input.readLine)
}
}
}
class CloseableResource[T](resource: =>T,onClose: T=>Unit){
def foreach(f: T=>Unit){
val r = resource
try{
f(r)
}
finally{
try{
onClose(r)
}
catch{
case e =>
println("error closing resource")
e.printStackTrace
}
}
}
}
object CloseableResource{
implicit def javaCloseableToCloseableResource[T <: java.io.Closeable](resource:T):CloseableResource[T] = new CloseableResource[T](resource,{_.close})
}
答案 4 :(得分:1)
此解决方案完全没有您想要的语法,但我认为它足够接近:)
def using[A <: {def close(): Unit}, B](resources: List[A])(f: List[A] => B): B =
try f(resources) finally resources.foreach(_.close())
using(List(new BufferedReader(new FileReader("in.txt")), new PrintWriter("out.txt"))) {
case List(in: BufferedReader, out: PrintWriter) => out.println(in.readLine())
}
当然,缺点是你必须在使用块中输入BufferedReader
和PrintWrter
类型。您可以添加一些魔法,这样您只需要List(in, out)
使用multiple ORed type bounds来使用A类型。
通过定义一些非常hacky和危险的隐式转换,您可以解决必须键入List
(和另一种方法来解决指定资源的类型),但我没有记录细节,因为它太危险了IMO。
答案 5 :(得分:1)
最好从程序路径中分离清理算法。
此解决方案允许您在范围内累积closeable。
范围清除将在块执行后发生,或者可以分离范围。然后可以在以后完成范围的清理。
通过这种方式,我们可以获得相同的便利性,仅限于单线程编程。
实用程序类:
import java.io.Closeable
object ManagedScope {
val scope=new ThreadLocal[Scope]();
def managedScope[T](inner: =>T):T={
val previous=scope.get();
val thisScope=new Scope();
scope.set(thisScope);
try{
inner
} finally {
scope.set(previous);
if(!thisScope.detatched) thisScope.close();
}
}
def closeLater[T <: Closeable](what:T): T = {
val theScope=scope.get();
if(!(theScope eq null)){
theScope.closeables=theScope.closeables.:+(what);
}
what;
}
def detatchScope(): Scope={
val theScope=scope.get();
if(theScope eq null) null;
else {
theScope.detatched=true;
theScope;
}
}
}
class Scope{
var detatched=false;
var closeables:List[Closeable]=List();
def close():Unit={
for(c<-closeables){
try{
if(!(c eq null))c.close();
} catch{
case e:Throwable=>{};
}
}
}
}
用法:
def checkSocketConnect(host:String, portNumber:Int):Unit = managedScope {
// The close later function tags the closeable to be closed later
val socket = closeLater( new Socket(host, portNumber) );
doWork(socket);
}
def checkFutureConnect(host:String, portNumber:Int):Unit = managedScope {
// The close later function tags the closeable to be closed later
val socket = closeLater( new Socket(host, portNumber) );
val future:Future[Boolean]=doAsyncWork(socket);
// Detatch the scope and use it in the future.
val scope=detatchScope();
future.onComplete(v=>scope.close());
}
答案 6 :(得分:1)
使用结构类型似乎有点矫枉过正,因为 java.lang.AutoCloseable 是预定的用法:
implicit class UsingExtension[A <: AutoCloseable](val resource: A) extends AnyVal {
def using[B](block: A => B): B = try block(resource) finally resource.close()
}
或者,如果您更喜欢扩展方法:
def using2[R1 <: AutoCloseable, R2 <: AutoCloseable, B](resource1: R1, resource2: R2)(block: (R1, R2) => B): B =
using(resource1) { _ =>
using(resource2) { _ =>
block(resource1, resource2)
}
}
使用2是可能的:
{{1}}
但imho非常难看 - 我更愿意在客户端代码中简单地嵌套这些使用语句。
答案 7 :(得分:0)
这是我对Scala中资源管理的解决方案:
def withResources[T <: AutoCloseable, V](r: => T)(f: T => V): V = {
val resource: T = r
require(resource != null, "resource is null")
var exception: Throwable = null
try {
f(resource)
} catch {
case NonFatal(e) =>
exception = e
throw e
} finally {
closeAndAddSuppressed(exception, resource)
}
}
private def closeAndAddSuppressed(e: Throwable,
resource: AutoCloseable): Unit = {
if (e != null) {
try {
resource.close()
} catch {
case NonFatal(suppressed) =>
e.addSuppressed(suppressed)
}
} else {
resource.close()
}
}
我在多个Scala应用程序中使用了此功能,包括在Spark执行程序中管理资源。并且应该意识到,我们还有其他更好的方法来管理资源,例如在CatsIO中:https://typelevel.org/cats-effect/datatypes/resource.html。如果您可以在Scala中使用纯FP没问题。
要回答最后一个问题,您绝对可以像这样嵌套资源:
withResource(r: File)(
r => {
withResource(a: File)(
anotherR => {
withResource(...)(...)
}
)
}
)
通过这种方式,不仅可以保护这些资源免遭泄漏,而且还可以按正确的顺序释放它们(例如堆栈)。就像CatsIO的Resource Monad一样。
答案 8 :(得分:0)
从Scala 2.13
开始,标准库提供了专用的资源管理实用程序:Using
。
更具体地说,在处理多种资源时可以使用Using#Manager
。
在我们的情况下,我们可以管理不同的资源,例如您的PrintWriter
或BufferedReader
,因为它们都实现了AutoCloseable
,以便从一个文件读取另一个文件,而没有无论如何,请随后关闭输入和输出资源:
import scala.util.Using
import java.io.{PrintWriter, BufferedReader, FileReader}
Using.Manager { use =>
val in = use(new BufferedReader(new FileReader("input.txt")))
val out = use(new PrintWriter("output.txt"))
out.println(in.readLine)
}
// scala.util.Try[Unit] = Success(())