Scalaにはどのような自動リソース管理の選択肢がありますか?


102

ScalaのWebでARM(自動リソース管理)の多くの例を見てきました。それを書くのは慣習のようですが、ほとんどは互いに似ています。私がやったものの、継続を使ってかなりクールの例を参照してください。

とにかく、そのコードの多くにはあるタイプまたは別のタイプの欠陥があるので、ここでStack Overflowにリファレンスを用意して、最も適切で適切なバージョンに投票できるようにするとよいと思いました。


コミュニティウィキではない場合、この質問はより多くの回答を生成しますか?コミュニティウィキアワードの評判で回答が投票されたかどうかを確認してください...
huynhjl

2
一意の参照により、ARMに別のレベルの安全性が追加され、close()が呼び出される前にリソースへの参照がマネージャに返されるようになります。thread.gmane.org/gmane.comp.lang.scala/19160/focus=19168
retronym

@retronym一意性プラグインは、継続というよりも、かなりの革命になると思います。そして実際、これはScalaで、遠くない将来に他の言語に移植される可能性が高いことの1つだと思います。これが出てきたら、必ずそれに応じて回答を編集しましょう。:-)
ダニエルC.ソブラル

1
複数のjava.lang.AutoCloseableインスタンスをネストできるようにする必要があるため、インスタンス化はそれぞれ、前のインスタンス化が成功したことに依存しているので、最終的に私にとって非常に便利なパターンを見つけました。StackOverflowの同様の質問に対する回答として書きました:stackoverflow.com/a/34277491/501113
chaotic3quilibrium

回答:


10

今のところ、Scala 2.13がようやくサポートtry with resourcesされました。使用 :) を使用して、例:

val lines: Try[Seq[String]] =
  Using(new BufferedReader(new FileReader("file.txt"))) { reader =>
    Iterator.unfold(())(_ => Option(reader.readLine()).map(_ -> ())).toList
  }

またはUsing.resource回避を使用してTry

val lines: Seq[String] =
  Using.resource(new BufferedReader(new FileReader("file.txt"))) { reader =>
    Iterator.unfold(())(_ => Option(reader.readLine()).map(_ -> ())).toList
  }

その他の例については、ドキュメントの使用をご覧ください。

自動リソース管理を実行するためのユーティリティ。これは、リソースを使用して操作を実行するために使用できます。その後、作成とは逆の順序でリソースを解放します。


Using.resourceバリアントも追加していただけませんか?
Daniel C. Sobral

@ DanielC.Sobral、確かに、ちょうどそれを追加しました。
chengpohi

Scala 2.12用にこれをどのように書きますか?ここでは類似しているusing方法は:def using[A <: AutoCloseable, B](resource: A) (block: A => B): B = try block(resource) finally resource.close()
マイクSlinn

75

2009年3月26日のChris Hansenのブログエントリ「ARM Blocks in Scala:Revisited」では、Martin OderskyFOSDEMプレゼンテーションのスライド21について説明してます。この次のブロックは、スライド21(許可あり)から直接取られたものです。

def using[T <: { def close() }]
    (resource: T)
    (block: T => Unit) 
{
  try {
    block(resource)
  } finally {
    if (resource != null) resource.close()
  }
}

-見積もりを終了-

次に、次のように呼び出すことができます。

using(new BufferedReader(new FileReader("file"))) { r =>
  var count = 0
  while (r.readLine != null) count += 1
  println(count)
}

このアプローチの欠点は何ですか?このパターンは、自動リソース管理が必要な場所の95%に対応しているようです...

編集:コードスニペットを追加


Edit2:デザインパターンを拡張する-pythonwithステートメントからインスピレーションを得て以下に対処します。

  • ブロックの前に実行するステートメント
  • 管理対象リソースに応じて例外を再スローする
  • 1つの単一のusingステートメントで2つのリソースを処理する
  • 暗黙的な変換とManagedクラスを提供することによるリソース固有の処理

これはScala 2.8でのことです。

trait Managed[T] {
  def onEnter(): T
  def onExit(t:Throwable = null): Unit
  def attempt(block: => Unit): Unit = {
    try { block } finally {}
  }
}

def using[T <: Any](managed: Managed[T])(block: T => Unit) {
  val resource = managed.onEnter()
  var exception = false
  try { block(resource) } catch  {
    case t:Throwable => exception = true; managed.onExit(t)
  } finally {
    if (!exception) managed.onExit()
  }
}

def using[T <: Any, U <: Any]
    (managed1: Managed[T], managed2: Managed[U])
    (block: T => U => Unit) {
  using[T](managed1) { r =>
    using[U](managed2) { s => block(r)(s) }
  }
}

class ManagedOS(out:OutputStream) extends Managed[OutputStream] {
  def onEnter(): OutputStream = out
  def onExit(t:Throwable = null): Unit = {
    attempt(out.close())
    if (t != null) throw t
  }
}
class ManagedIS(in:InputStream) extends Managed[InputStream] {
  def onEnter(): InputStream = in
  def onExit(t:Throwable = null): Unit = {
    attempt(in.close())
    if (t != null) throw t
  }
}

implicit def os2managed(out:OutputStream): Managed[OutputStream] = {
  return new ManagedOS(out)
}
implicit def is2managed(in:InputStream): Managed[InputStream] = {
  return new ManagedIS(in)
}

def main(args:Array[String]): Unit = {
  using(new FileInputStream("foo.txt"), new FileOutputStream("bar.txt")) { 
    in => out =>
    Iterator continually { in.read() } takeWhile( _ != -1) foreach { 
      out.write(_) 
    }
  }
}

2
代替案はありますが、私はそれに何か問題があることを示唆するつもりはありません。Stack Overflowで、ここでそれらすべての答えを求めています。:-)
ダニエルC.ソブラル

5
標準APIにこのようなものがあるかどうか知っていますか?いつも自分のためにこれを書かなければならない雑用のようです。
ダニエルダラボス2014年

これが投稿されてからしばらく時間が経過しましたが、最初の解決策は、おそらくここでは発生しないoutコンストラクターがスローした場合に内部ストリームを閉じませんが、これが悪い場合もあります。クローズも投げることができます。致命的な例外も区別されません。2つ目はコードの臭いがどこにでもあり、1つ目よりも利点がありません。実際の型を失うことさえあるので、ZipInputStreamのようなものには役に立たないでしょう。
steinybot 2016年

ブロックがイテレータを返す場合、これをどのように行うことをお勧めしますか?
ホルヘマチャド

62

ダニエル、

私は最近、自動リソース管理用のscala-armライブラリをデプロイしました。ドキュメントはこちらにあります:https : //github.com/jsuereth/scala-arm/wiki

このライブラリは、次の3つの使用方法をサポートしています(現在)。

1)命令型/表現型:

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

2)モナディックスタイル

import resource._
import java.io._
val lines = for { input <- managed(new FileInputStream("test.txt"))
                  val bufferedReader = new BufferedReader(new InputStreamReader(input)) 
                  line <- makeBufferedReaderLineIterator(bufferedReader)
                } yield line.trim()
lines foreach println

3)区切り付き継続スタイル

これが「エコー」tcpサーバーです。

import java.io._
import util.continuations._
import resource._
def each_line_from(r : BufferedReader) : String @suspendable =
  shift { k =>
    var line = r.readLine
    while(line != null) {
      k(line)
      line = r.readLine
    }
  }
reset {
  val server = managed(new ServerSocket(8007)) !
  while(true) {
    // This reset is not needed, however the  below denotes a "flow" of execution that can be deferred.
    // One can envision an asynchronous execuction model that would support the exact same semantics as below.
    reset {
      val connection = managed(server.accept) !
      val output = managed(connection.getOutputStream) !
      val input = managed(connection.getInputStream) !
      val writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(output)))
      val reader = new BufferedReader(new InputStreamReader(input))
      writer.println(each_line_from(reader))
      writer.flush()
    }
  }
}

このコードはResourceタイプの特性を利用しているため、ほとんどのリソースタイプに適応できます。closeメソッドまたはdisposeメソッドのいずれかを使用して、クラスに対して構造型付けを使用するフォールバックがあります。ドキュメントをチェックして、追加する便利な機能を思いついたら教えてください。


1
はい、見ました。コードを調べて、いくつかのことを実行する方法を確認したいのですが、今は忙しすぎます。とにかく、質問の目的は信頼できるARMコードへの参照を提供することなので、これを受け入れられた答えにします。
ダニエルC.ソブラル2010

18

ここだジェームズIRYの継続を使用したソリューションは:

// standard using block definition
def using[X <: {def close()}, A](resource : X)(f : X => A) = {
   try {
     f(resource)
   } finally {
     resource.close()
   }
}

// A DC version of 'using' 
def resource[X <: {def close()}, B](res : X) = shift(using[X, B](res))

// some sugar for reset
def withResources[A, C](x : => A @cps[A, C]) = reset{x}

比較のための継続ありとなしのソリューションを次に示します。

def copyFileCPS = using(new BufferedReader(new FileReader("test.txt"))) {
  reader => {
   using(new BufferedWriter(new FileWriter("test_copy.txt"))) {
      writer => {
        var line = reader.readLine
        var count = 0
        while (line != null) {
          count += 1
          writer.write(line)
          writer.newLine
          line = reader.readLine
        }
        count
      }
    }
  }
}

def copyFileDC = withResources {
  val reader = resource[BufferedReader,Int](new BufferedReader(new FileReader("test.txt")))
  val writer = resource[BufferedWriter,Int](new BufferedWriter(new FileWriter("test_copy.txt")))
  var line = reader.readLine
  var count = 0
  while(line != null) {
    count += 1
    writer write line
    writer.newLine
    line = reader.readLine
  }
  count
}

そして、ここにTiark Rompfの改善の提案があります:

trait ContextType[B]
def forceContextType[B]: ContextType[B] = null

// A DC version of 'using'
def resource[X <: {def close()}, B: ContextType](res : X): X @cps[B,B] = shift(using[X, B](res))

// some sugar for reset
def withResources[A](x : => A @cps[A, A]) = reset{x}

// and now use our new lib
def copyFileDC = withResources {
 implicit val _ = forceContextType[Int]
 val reader = resource(new BufferedReader(new FileReader("test.txt")))
 val writer = resource(new BufferedWriter(new FileWriter("test_copy.txt")))
 var line = reader.readLine
 var count = 0
 while(line != null) {
   count += 1
   writer write line
   writer.newLine
   line = reader.readLine
 }
 count
}

BufferedWriterコンストラクターが失敗したときに、(new BufferedWriter(new FileWriter( "test_copy.txt")))を使用しないと問題が発生しますか?すべてのリソースは、usingブロックでラップする必要があります...
Jaap 2013

@JaapこれはOracleによって提案されたスタイルです。BufferedWriterはチェック済み例外をスローしないため、例外がスローされても、プログラムはその例外からの回復を期待されていません。
ダニエルC.ソブラル2013

7

ScalaでARMを実行するための段階的な4ステップの進化が見られます。

  1. ARMなし:汚れ
  2. クロージャのみ:より良いが、複数のネストされたブロック
  3. 継続モナド:入れ子を平坦化するためにForを使用しますが、2ブロックでの不自然な分離
  4. 直接的なスタイルの継続:Nirava、aha!これは、最もタイプセーフな代替手段でもあります。withResourceブロックの外のリソースはタイプエラーになります。

1
ちなみに、ScalaのCPSはモナドを通じて実装されます。:-)
ダニエルC.ソブラル

1
Mushtaq、3)継続のモナドではないモナドでリソース管理を実行できます。4)withResources / resourceで区切られた継続コードを使用したリソース管理は、「使用する」よりも安全です。それを必要とするリソースを管理することを忘れることはまだ可能です。using(new Resource()){first => val second = new Resource()//おっと!//リソースを使用する} //最初に閉じられるのはwithResources {val first = resource(new Resource())val second = new Resource()//おっと //リソースを使用します...} //最初にクローズされます
James Iry

2
Daniel、ScalaのCPSは、あらゆる関数型言語のCPSに似ています。モナドを使用するのは、区切られた継続です。
James Iry、2010

James、よく説明してくれてありがとう。インドに座って、私はあなたのBASEトークのために私がそこにいればよかったのにと思いました。これらのスライドをオンラインにしたときに表示されるのを待ちます:)
Mushtaq Ahmed

6

軽量(10行のコード)のARMが、より優れたファイルに含まれています。参照:https : //github.com/pathikrit/better-files#lightweight-arm

import better.files._
for {
  in <- inputStream.autoClosed
  out <- outputStream.autoClosed
} in.pipeTo(out)
// The input and output streams are auto-closed once out of scope

ライブラリ全体が不要な場合の実装方法は次のとおりです。

  type Closeable = {
    def close(): Unit
  }

  type ManagedResource[A <: Closeable] = Traversable[A]

  implicit class CloseableOps[A <: Closeable](resource: A) {        
    def autoClosed: ManagedResource[A] = new Traversable[A] {
      override def foreach[U](f: A => U) = try {
        f(resource)
      } finally {
        resource.close()
      }
    }
  }

これはかなりいいです。私はこのアプローチに似たものを採用しましたが、foreachではなくCloseableOpsのmapand flatMapメソッドを定義しました。
EdgeCaseBerg 2017

1

Typeクラスの使用について

trait GenericDisposable[-T] {
   def dispose(v:T):Unit
}
...

def using[T,U](r:T)(block:T => U)(implicit disp:GenericDisposable[T]):U = try {
   block(r)
} finally { 
   Option(r).foreach { r => disp.dispose(r) } 
}

1

別の選択肢は、ChoppyのLazy TryCloseモナドです。データベース接続には非常に適しています。

val ds = new JdbcDataSource()
val output = for {
  conn  <- TryClose(ds.getConnection())
  ps    <- TryClose(conn.prepareStatement("select * from MyTable"))
  rs    <- TryClose.wrap(ps.executeQuery())
} yield wrap(extractResult(rs))

// Note that Nothing will actually be done until 'resolve' is called
output.resolve match {
    case Success(result) => // Do something
    case Failure(e) =>      // Handle Stuff
}

そしてストリームで:

val output = for {
  outputStream      <- TryClose(new ByteArrayOutputStream())
  gzipOutputStream  <- TryClose(new GZIPOutputStream(outputStream))
  _                 <- TryClose.wrap(gzipOutputStream.write(content))
} yield wrap({gzipOutputStream.flush(); outputStream.toByteArray})

output.resolve.unwrap match {
  case Success(bytes) => // process result
  case Failure(e) => // handle exception
}

詳細はこちら:https : //github.com/choppythelumberjack/tryclose


0

これは@chengpohiの回答です。Scala2.13だけでなく、Scala 2.8+でも動作するように変更されています(そうです、Scala 2.13でも動作します)。

def unfold[A, S](start: S)(op: S => Option[(A, S)]): List[A] =
  Iterator
    .iterate(op(start))(_.flatMap{ case (_, s) => op(s) })
    .map(_.map(_._1))
    .takeWhile(_.isDefined)
    .flatten
    .toList

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

val lines: Seq[String] =
  using(new BufferedReader(new FileReader("file.txt"))) { reader =>
    unfold(())(_ => Option(reader.readLine()).map(_ -> ())).toList
  }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.