Quelles alternatives de gestion automatique des ressources existent pour Scala?

J'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 ressemblent à peu près les uns des autres. Je ai fait voir un exemple assez cool en utilisant des continuations, cependant.

En tout cas, beaucoup de ce code a des défauts d'un type ou d'un autre, donc j'ai pensé que ce serait une bonne idée d'avoir une référence ici sur Stack Overflow, où nous pouvons voter les versions les plus correctes et appropriées.

98
demandé sur Daniel C. Sobral 2010-02-05 16:23:17

7 réponses

Daniel,

J'ai récemment déployé la bibliothèque scala-arm pour la gestion automatique des ressources. Vous pouvez trouver la documentation ici: http://wiki.github.com/jsuereth/scala-arm/

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

1) Impératif/pour 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) Continuations délimitées-style

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 utilise une Ressource type-trait, il est donc capable de s'adapter à la plupart des types de ressources. Il a un repli pour utiliser le typage structurel contre les classes avec une méthode close ou dispose. Veuillez consulter la documentation et laissez-moi savoir si vous pensez à des fonctionnalités pratiques à ajouter.

59
répondu jsuereth 2010-08-09 13:42:04

L'entrée de blog de Chris Hansen 'ARM Blocks in Scala: Revisited' du 26/03/09 parle de la diapositive 21 de la présentation de Martin Odersky FOSDEM. Ce bloc suivant est tiré directement de la diapositive 21 (avec autorisation):

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

--fin de citation--

Alors 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 semblerait répondre à 95% de l'endroit où j'aurais besoin d'une gestion automatique des ressources...

Modifier: Ajouté extrait de code


Edit2: étendre le modèle de conception-en s'inspirant de la déclaration et de l'adressage python with:

  • instructions à exécuter avant le bloc
  • exception de relance en fonction de la ressource gérée
  • gérer deux ressources avec une seule instruction using
  • gestion spécifique à la ressource en fournissant une conversion implicite et une classe Managed

C'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(_) 
    }
  }
}
72
répondu huynhjl 2010-02-10 04:33:24

Ici James Iri solution à l'aide de suites:

// 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 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 D'amélioration de 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
}
18
répondu Daniel C. Sobral 2017-05-23 12:26:33

Daniel, c'est bien que tu aies demandé ça. Je suis moi-même intrigué après avoir vu le code de James Iry. Je vois une évolution progressive en 4 étapes pour faire ARM dans Scala:

  1. Pas de bras: Saleté
  2. seulement les fermetures: mieux, mais plusieurs blocs imbriqués
  3. Suite Monade: utiliser pour aplatir l'imbrication, mais la séparation non naturelle dans 2 blocs
  4. continuations de style Direct: Nirava, aha! C'est aussi l'alternative la plus sûre: une ressource en dehors du bloc withResource sera de type erreur.

Ce que j'aimerais vraiment voir une présentation décrivant ces. Ce sera très éducatif et devrait convaincre les begots qu'il y a un monde au-delà des monades:)

8
répondu Mushtaq Ahmed 2010-02-08 03:38:36

Il y a un bras léger (10 lignes de code) inclus avec better-files. 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 implémenté 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()
      }
    }
  }
5
répondu pathikrit 2015-10-10 17:50:37

Que diriez-vous d'utiliser les 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) } 
}
1
répondu Santhosh Sath 2017-07-06 17:42:12

Une autre alternative est la Monade Tryclose paresseuse de Choppy. C'est assez bon 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 les 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

1
répondu ChoppyTheLumberjack 2018-01-17 14:36:57