Quelles sont les alternatives de gestion automatique des ressources existent pour Scala?

StackOverflow https://stackoverflow.com/questions/2207425

  •  18-09-2019
  •  | 
  •  

Question

Je l'ai vu de nombreux exemples d'ARM (gestion automatique des ressources) sur le web pour Scala. Il semble être un rite de passage pour écrire un, bien que la plupart regardent un peu comme un autre. I ne voir un exemple assez cool en utilisant continuations, cependant.

En tout cas, beaucoup de ce code a des défauts d'un type ou d'une autre, alors je me suis dit que ce serait une bonne idée d'avoir une référence ici sur Stack Overflow, où l'on peut voter les versions les plus correctes et appropriées.

Était-ce utile?

La solution

Pour l'instant Scala 2,13 a finalement pris en charge: try with resources en utilisant Utiliser :), Exemple :

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

ou en utilisant Using.resource éviter Try

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

Vous pouvez trouver plus d'exemples de En utilisant doc.

  

Un utilitaire pour effectuer la gestion automatique des ressources. Il peut être utilisé pour effectuer une opération à l'aide des ressources, après quoi il libère les ressources dans l'ordre inverse de leur création.

Autres conseils

Chris Hansen « ARM blocs en Scala: Revisited » de 26/03/09 parle de sur 21 diapositive au sujet de Martin Odersky FOSDEM présentation . Ce bloc suivant est pris directement à partir de glissière 21 (avec la permission):

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

- fin de citation -

Ensuite, nous pouvons appeler comme ceci:

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

Quels sont les inconvénients de cette approche? Ce modèle semble répondre à 95% de l'endroit où je besoin d'une gestion automatique des ressources ...

Edit: ajouté extrait de code


Edit2: l'extension du modèle de conception - se inspirant de la déclaration de with python et adressage:

  • instructions à exécuter avant le bloc
  • re-jeter exception en fonction de la ressource gérée
  • manipulation deux ressources avec une seule instruction à l'aide
  • un traitement spécifique des ressources en fournissant une conversion implicite et une classe Managed

est avec 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,

Je viens récemment déployé la bibliothèque scala-bras pour la gestion automatique des ressources. Vous trouverez ici la documentation: https://github.com/jsuereth/scala-arm/wiki

Cette bibliothèque prend en charge trois styles d'utilisation (actuellement):

1) impératif / en-expression:

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

2) style Monadique

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) de style continuations Délimité

Voici un serveur tcp "echo":

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

Le code fait usage d'une ressource de type trait, il est donc en mesure de s'adapter à la plupart des types de ressources. Il a une solution de repli à utiliser la saisie structurelle contre les classes avec soit une méthode proche ou Éliminez. S'il vous plaît consulter la documentation et laissez-moi savoir si vous pensez de toutes les fonctionnalités pratiques à ajouter.

Voici solution James Iry utilisant continuations:

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

Voici les solutions avec et sans continuations pour la comparaison:

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
}

Et voici la suggestion de Tiark Rompf d'amélioration:

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
}

Je vois une évolution progressive 4 étapes pour faire ARM à Scala:

  1. Non ARM: Dirt
  2. Seules les fermetures: mieux, mais plusieurs blocs imbriqués
  3. Suite Monad: Utiliser pour aplatir l'emboîtement, mais la séparation non naturel en 2 blocs
  4. continuations style direct: Nirava, aha! Ceci est aussi la plus alternative de type sécurisé:. Une ressource externe bloc tagresource sera erreur de type

Il est léger (10 lignes de code) ARM inclus avec de meilleurs-fichiers. Voir: 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

Voici comment il est mis en œuvre si vous ne voulez pas toute la bibliothèque:

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

Comment l'utilisation des classes de 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) } 
}

Une autre alternative est paresseux TryClose monade de saccadée. Il est assez bien avec les connexions de base de données:

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
}

Et avec des flux:

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
}

Plus d'infos ici: https://github.com/choppythelumberjack/tryclose

Voici @ réponse de chengpohi, modifiée de sorte qu'il fonctionne avec Scala 2.8+, au lieu de simplement Scala 2,13 (oui, il fonctionne avec Scala 2,13 également):

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
  }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top