'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個のタプル/製品クラスがライブラリに記述されているという事実を再利用していると思います...この構文はsingネストされたusing
(noしゃれが意図されていました)が、それは興味深いパズルでした。
編集:レトロニムによって提案されているように、val割り当てをcase (in, out, other)
に置き換えました。
開始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(())
残念ながら、標準のScalaでは、任意のタイプの任意の長さのパラメーターリストはサポートされていません。
いくつかの言語を変更することで、このようなことができる場合があります(変数パラメーターリストをHListとして渡すことができるようにするため。必要なものの約1/3については、 ここ を参照してください)。
今のところ、最善の方法は、タプルと関数が行うことを実行することです。必要な数のNに対してusingNを実装します。
もちろん、2つは簡単です。
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() }
}
さらに必要な場合は、ソースコードを生成するものを作成する価値があります。
これは、Java.io.Closeableであるアイテムの自動リソース管理ブロックとして、理解のためにscalaを使用できるようにする例ですが、簡単に拡張して、 closeメソッドを持つオブジェクト。
この使用法はusingステートメントにかなり近いようであり、1つのブロックに必要な数のリソースを簡単に定義できます。
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})
}
Java.lang.AutoCloseableは使用する予定であるため、構造型の使用は少しやり過ぎのようです。
def using[A <: AutoCloseable, B](resource: A)(block: A => B): B =
try block(resource) finally resource.close()
または、拡張メソッドが必要な場合:
implicit class UsingExtension[A <: AutoCloseable](val resource: A) extends AnyVal {
def using[B](block: A => B): B = try block(resource) finally resource.close()
}
using2が可能です:
def using2[R1 <: AutoCloseable, R2 <: AutoCloseable, B](resource1: R1, resource2: R2)(block: (R1, R2) => B): B =
using(resource1) { _ =>
using(resource2) { _ =>
block(resource1, resource2)
}
}
しかし、私はかなり醜いです-私はクライアントコードのステートメントを使用してこれらを単純にネストしたいと思います。
クリーンアップアルゴリズムをプログラムパスから切り離すことをお勧めします。
このソリューションを使用すると、スコープ内にクロージャブルを蓄積できます。
スコープのクリーンアップは、ブロックが実行された後に発生します。または、スコープを切り離すことができます。スコープのクリーニングは後で実行できます。
このようにして、シングルスレッドプログラミングに限定されることなく、同じ便利さを得ることができます。
ユーティリティクラス:
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());
}
このソリューションはquiteに必要な構文を持っていませんが、十分に近いと思います:)
_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())
}
_
もちろん、欠点は、usingブロックにタイプBufferedReader
とPrintWrter
を入力する必要があることです。使用するタイプAに 複数のOR型境界 を使用することで、List(in, out)
が必要になるように魔法を追加できる場合があります。
かなりハッキーで危険な暗黙の変換を定義することで、List
と入力する必要がなくなります(リソースの型を指定する別の方法もあります)が、IMOが危険すぎるため、詳細については説明していません。
これが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のリソースモナドと同じ動作。