Domanda

Ho visto molti esempi di ARM (gestione automatica delle risorse) sul web per Scala.Sembra essere un rito di passaggio scriverne uno, anche se la maggior parte si somiglia molto.IO fatto guarda un esempio piuttosto interessante usando le continuazioni, però.

In ogni caso, gran parte di quel codice presenta difetti di un tipo o di un altro, quindi ho pensato che sarebbe stata una buona idea avere un riferimento qui su Stack Overflow, dove possiamo votare le versioni più corrette e appropriate.

È stato utile?

Soluzione

Per ora Scala 2.13 ha finalmente sostenuto: try with resources utilizzando Utilizzando :), Esempio :

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

o utilizzando Using.resource evitare di Try

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

Si possono trovare altri esempi da utilizzando doc .

  

Un programma di utilità per l'esecuzione di gestione automatica delle risorse. Può essere utilizzato per eseguire un'operazione che utilizza risorse, dopo di che rilascia le risorse in ordine inverso della loro creazione.

Altri suggerimenti

Quello di Chris Hansen post sul blog "Blocchi ARM in Scala:Rivisitato' dal 26/03/09 parla della diapositiva 21 di Martin Odersky Presentazione del FOSDEM.Il blocco successivo è preso direttamente dalla diapositiva 21 (con autorizzazione):

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

--fine citazione--

Quindi possiamo chiamare in questo modo:

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

Quali sono gli svantaggi di questo approccio?Questo modello sembrerebbe risolvere il 95% dei casi in cui avrei bisogno di una gestione automatica delle risorse...

Modificare: aggiunto snippet di codice


Modifica2: estendendo il modello di progettazione, prendendo ispirazione da Python with dichiarazione e indirizzo:

  • istruzioni da eseguire prima del blocco
  • eccezione di rilancio in base alla risorsa gestita
  • gestire due risorse con un'unica istruzione using
  • gestione specifica della risorsa fornendo una conversione implicita e a Managed classe

Questo è con 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,

Ho appena recentemente implementato la libreria scala-braccio per la gestione automatica delle risorse. È possibile trovare la documentazione qui: https://github.com/jsuereth/scala-arm/wiki

Questa libreria supporta tre stili di utilizzo (attualmente):

1) Imperativo / per l'espressione:

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

2) Monadica stile

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) delimitato Continuazioni stile

Ecco un "echo" server 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()
    }
  }
}

Il codice fa uso di una risorsa di tipo-tratto, quindi è in grado di adattarsi alla maggior parte dei tipi di risorse. Ha un fallback da utilizzare tipizzazione strutturale contro classi sia con un metodo chiudere o dispose. Si prega di verificare la documentazione e fatemi sapere se si pensa di tutte le funzioni a portata di mano da aggiungere.

Ecco soluzione James Iry utilizzando continuazioni:

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

Ecco le soluzioni con e senza continuazioni per il confronto:

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
}

Ed ecco il suggerimento di Tiark Rompf di miglioramento:

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
}

Vedo una graduale evoluzione 4 passo per fare ARM a Scala:

  1. No ARM: Sabbia
  2. Solo chiusure: meglio, ma più blocchi annidati
  3. Perfezionamento Monade: usare per appiattire il nesting, ma separazione innaturale in 2 blocchi
  4. continuazioni stile diretto: Nirava, aha! Questo è anche il più type-safe alternativa:. Una risorsa al di fuori del blocco withResource sarà tipo di errore

Non è leggero (10 righe di codice) Braccio incluso con migliori-files. Vedere: 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

Ecco come viene attuata se non si desidera che l'intera libreria:

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

Come sull'utilizzo classi di tipo

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

Un'altra alternativa è pigro TryClose monade di mosso. E 'piuttosto bene con connessioni al database:

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
}

E con i flussi:

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
}

Più informazioni su: https://github.com/choppythelumberjack/tryclose

Ecco la risposta di @ chengpohi, modificato in modo che funziona con Scala 2.8 o successiva, invece di Scala 2.13 (sì, funziona con Scala 2.13 anche):

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
  }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top