开发者

"using" function

开发者 https://www.devze.com 2022-12-21 23:53 出处:网络
I\'ve defined \'using\' function as following: def using[A, B <: {def close(): Unit开发者_Python百科}] (closeable: B) (f: B => A): A =

I've defined 'using' function as following:

def using[A, B <: {def close(): Unit开发者_Python百科}] (closeable: B) (f: B => A): A =
  try { f(closeable) } finally { closeable.close() }

I can use it like that:

using(new PrintWriter("sample.txt")){ out =>
  out.println("hellow world!")
}

now I'm curious how to define 'using' function to take any number of parameters, and be able to access them separately:

using(new BufferedReader(new FileReader("in.txt")), new PrintWriter("out.txt")){ (in, out) =>
  out.println(in.readLIne)
}


Starting Scala 2.13, the standard library provides a dedicated resource management utility: Using.

More specifically, the Using#Manager can be used when dealing with several resources.

In our case, we can manage different resources such as your PrintWriter or BufferedReader as they both implement AutoCloseable, in order to read and write from a file to another and, no matter what, close both the input and the output resource afterwards:

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(())


Someone has already done this—it's called Scala ARM.

From the readme:

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


I've been thinking about this and I thought maybe there was an other way to address this. Here is my take on supporting "any number" of parameters (limited by what tuples provide):

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!")
    }
  }
}

I think I'm reusing the fact that 22 tuple/product classes have been written in the library... I don't think this syntax is clearer than using nested using (no pun intended), but it was an interesting puzzle.


using structural typing seems like a little overkill since java.lang.AutoCloseable is predestined for usage:

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

or, if you prefer extension methods:

  implicit class UsingExtension[A <: AutoCloseable](val resource: A) extends AnyVal {
    def using[B](block: A => B): B = try block(resource) finally resource.close()
  }

using2 is possible:

  def using2[R1 <: AutoCloseable, R2 <: AutoCloseable, B](resource1: R1, resource2: R2)(block: (R1, R2) => B): B =
    using(resource1) { _ =>
      using(resource2) { _ =>
        block(resource1, resource2)
      }
    }

but imho quite ugly - I would prefer to simply nest these using statements in the client code.


Unfortunately, there isn't support for arbitrary-length parameter lists with arbitrary types in standard Scala.

You might be able to do something like this with a couple of language changes (to allow variable parameter lists to be passed as HLists; see here for about 1/3 of what's needed).

Right now, the best thing to do is just do what Tuple and Function do: implement usingN for as many N as you need.

Two is easy enough, of course:

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() }
}

If you need more, it's probably worth writing something that'll generate the source code.


Here is an example that allows you to use the scala for comprehension as an automatic resource management block for any item that is a java.io.Closeable, but it could easily be expanded to work for any object with a close method.

This usage seems pretty close to the using statement and allows you to easily have as many resources defined in one block as you want.

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})
}


It is a good idea to detatch the cleanup algorithm from the program path.

This solution lets you accumulate closeables in a scope.
The scope cleanup will happen on after the block is executed, or the scope can be detached. The cleaning of the scope can then be done later.

This way we get the same convenience whitout being limited to single thread programming.

The utility class:

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=>{};
      }
    }
  }
}

The usage:

  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());
  }


This solution doesn't quite have the syntax you desire, but I think it's close enough :)

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())
}

Of course the down side is you have to type out the types BufferedReader and PrintWrter in the using block. You might be able to add some magic so that you just need List(in, out) by using multiple ORed type bounds for type A in using.

By defining some pretty hacky and dangerous implicit conversions you can get around having to type List (and another way to get around specifying types for the resources), but I haven't documented the detail as it's too dangerous IMO.


here is my solution to the resource management in 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()
    }
  }

I used this in multiple Scala apps including managing resources in Spark executors. and one should be aware that we are other even better ways to manage resource like in CatsIO: https://typelevel.org/cats-effect/datatypes/resource.html. if you are ok with pure FP in Scala.

to answer your last question, you can definitely nest the resource like this:

withResource(r: File)(
  r => {
    withResource(a: File)(
      anotherR => {
        withResource(...)(...)
      }
    )
  }
)

this way, not just that those resources are protected from leaking, they will also be released in the correct order(like stack). same behaviour like the Resource Monad from CatsIO.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号