Frage

Ich habe viele Beispiele für ARM (automatisches Ressourcenmanagement), die im Internet für Scala gesehen. Es scheint ein Ritus-of-Passage zu sein, einen zu schreiben, obwohl die meisten ziemlich aussehen miteinander. I hat ein ziemlich cooles Beispiel sieht Fortsetzungen mit, though.

Auf jeden Fall eine Menge von diesem Code hat Mängel der einen oder anderen Art, so dass ich dachte, es wäre eine gute Idee, einen Verweis hier auf Stack-Überlauf haben, wo wir die richtige und angemessene Versionen stimmen kann.

War es hilfreich?

Lösung

Scala 2,13 schließlich unterstützt hat: try with resources durch den Einsatz Mit :), Beispiel :

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

oder mit Using.resource vermeiden Try

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

Sie können mehr Beispiele von Mit doc.

  

Ein Dienstprogramm für das Ausführen eines automatischen Ressourcenmanagement. Es kann verwendet werden, um einen Betrieb mit Ressourcen auszuführen, wonach sie die Ressourcen in umgekehrter Reihenfolge ihrer Entstehung veröffentlicht.

Andere Tipps

Chris Hansen Blog ‚ARM-Blöcke in Scala: Revisited‘von 3/26/09 spricht über über Schieber 21 von Martin Odersky FOSDEM Präsentation . Dieser nächste Block genommen werden gerade von Schieber 21 (mit freundlicher Genehmigung):

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

- Zitat Ende -

Dann können wir wie folgt aufrufen:

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

Was sind die Nachteile dieses Ansatzes? Dieses Muster scheint 95% der Adresse, wo ich die automatische Ressourcenmanagement benötigen würde ...

Edit: hinzugefügt Code-Schnipsel


Edit2: , um das Design-Muster erstreckt - Inspiration von Python with Aussage zu nehmen und Adressierung:

  • Aussagen vor dem Block laufen
  • re werfenden Ausnahme je nach verwalteten Ressource
  • Umgang mit zwei Ressourcen mit einer einzigen Anweisung using
  • ressourcenspezifische Handhabung durch eine implizite Konvertierung und eine Managed Klasse Bereitstellung

Dies ist mit 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(_) 
    }
  }
}

Daniel,

Ich habe gerade vor kurzem die scala-Arm-Bibliothek für die automatische Ressourcenverwaltung im Einsatz. Sie können die Dokumentation finden Sie hier: https://github.com/jsuereth/scala-arm/wiki

Diese Bibliothek unterstützt drei Arten der Nutzung (derzeit):

1) Imperative / for-Ausdruck:

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

2) Monadic-style

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) mit Trennzeichen Fortsetzungen-style

Hier ist ein "Echo" TCP-Server:

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

Der Code macht Verwendungen einer Ressourcentyp-Eigenschaft, so dass es in der Lage der meisten Ressourcentypen anzupassen. Es hat einen Rückfall strukturelle Typisierung verwendet gegen Klassen entweder mit einer engen oder dispose-Methode. Bitte überprüfen Sie die Dokumentation aus und lassen Sie mich wissen, wenn Sie von irgendwelchen praktischen Funktionen denken hinzuzufügen.

Hier ist James Iry Lösung Fortsetzungen mit:

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

Hier sind die Lösungen mit und ohne Fortsetzungen zum Vergleich:

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
}

Und hier ist Tiark Rompf Vorschlag der Besserung:

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
}

Ich sehe eine allmähliche 4 Schritt Entwicklung für ARM in Scala zu tun:

  1. No ARM: Schmutz
  2. Nur Schließungen: Besser, aber mehrere verschachtelte Blöcke
  3. Fortsetzung Monad: Für den Einsatz der Verschachtelung, aber unnatürliche Trennung in Blöcke 2
  4. abzuflachen
  5. Direkt Stil Fortsetzungen: Nirava, aha! Dies ist auch die typsichere Alternative: a. Ressource außerhalb mitresource Block wird Typ Fehler sein

Es ist leicht (10 Zeilen Code) ARM mit besser Dateien enthalten. Siehe: 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

Hier ist, wie es umgesetzt wird, wenn Sie nicht die ganze Bibliothek wollen:

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

Wie wäre es Typklassen mit

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

Eine weitere Alternative ist Choppy faule TryClose Monade. Es ist ziemlich gut mit Datenbankverbindungen:

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
}

Und mit Strömen:

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
}

Weitere Informationen hier: https://github.com/choppythelumberjack/tryclose

Hier @ chengpohi Antwort, so modifiziert, dass es funktioniert mit Scala 2.8+, statt nur Scala 2,13 (ja, es funktioniert mit Scala 2.13 auch):

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
  }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top