Ответ 1
Кто-то уже это сделал, он называется Scala ARM.
Из файла readme:
import resource._
for(input <- managed(new FileInputStream("test.txt")) {
// Code that uses the input as a FileInputStream
}
Я определил функцию '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)
}
Кто-то уже это сделал, он называется Scala ARM.
Из файла readme:
import resource._
for(input <- managed(new FileInputStream("test.txt")) {
// Code that uses the input as a FileInputStream
}
Я думал об этом, и я думал, что это был другой способ справиться с этим. Вот мой подход к поддержке "любого числа" параметров (ограниченный тем, что кортежи предоставляют):
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.
К сожалению, нет поддержки списков параметров произвольной длины с произвольными типами в стандартном 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() }
}
Если вам нужно больше, возможно, стоит написать что-то, что будет генерировать исходный код.
Вот пример, который позволяет использовать 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})
}
Это решение не совсем соответствует синтаксису, но я думаю, что он достаточно близко:)
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.
Рекомендуется отменить алгоритм очистки из пути к программе.
Это решение позволяет накапливать закрытые объекты в области.
Очистка области будет выполняться после того, как блок будет выполнен, или область может быть отсоединена. Затем очистка области может быть выполнена позже.
Таким образом мы получаем одно и то же удобство, не ограничиваясь одним программированием нитей.
Класс утилиты:
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());
}