Подтвердить что ты не робот

Scala: функция "использование"

Я определил функцию 'using' следующим образом:

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", чтобы принимать любое количество параметров и иметь доступ к ним отдельно:

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

Ответ 1

Кто-то уже это сделал, он называется Scala ARM.

Из файла readme:

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

Ответ 2

Я думал об этом, и я думал, что это был другой способ справиться с этим. Вот мой подход к поддержке "любого числа" параметров (ограниченный тем, что кортежи предоставляют):

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 (не предназначенных для каламбур), но это было интересная головоломка.

edit: заменил назначение val на case (in, out, other), как предложено retronym.

Ответ 3

К сожалению, нет поддержки списков параметров произвольной длины с произвольными типами в стандартном Scala.

Возможно, вы сможете сделать что-то подобное с помощью нескольких языковых изменений (чтобы позволить перечислять списки переменных параметров как HLists, см. здесь примерно на 1/3 того, что нужно).

В настоящее время лучше всего сделать то, что делают Tuple и Function: реализовать useN для количества 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() }
}

Если вам нужно больше, возможно, стоит написать что-то, что будет генерировать исходный код.

Ответ 4

Вот пример, который позволяет использовать scala для понимания как автоматический блок управления ресурсами для любого элемента, который является java.io.Closeable, но его можно легко расширить, чтобы работать для любого объекта с закрытием метод.

Это использование кажется довольно близким к оператору 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})
}

Ответ 5

Это решение не совсем соответствует синтаксису, но я думаю, что он достаточно близко:)

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), используя несколько ограничений типа ORed для используемого типа A.

Определив некоторые довольно хакерские и опасные неявные преобразования, вы можете столкнуться с необходимостью набирать List (и еще один способ обойти определения типов для ресурсов), но я не документировал детали, поскольку это слишком опасно для IMO.

Ответ 6

Рекомендуется отменить алгоритм очистки из пути к программе.

Это решение позволяет накапливать закрытые объекты в области.
Очистка области будет выполняться после того, как блок будет выполнен, или область может быть отсоединена. Затем очистка области может быть выполнена позже.

Таким образом мы получаем одно и то же удобство, не ограничиваясь одним программированием нитей.

Класс утилиты:

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