Pregunta

He visto muchos ejemplos de ARM (gestión automática de recursos) en la web de Scala. Parece ser un rito de paso para escribir uno, aunque la mayoría se parecen mucho entre sí. I hicieron ver un ejemplo muy bien usando continuaciones, sin embargo.

En cualquier caso, gran parte de ese código tiene defectos de un tipo u otro, por lo que pensé que sería una buena idea tener una referencia aquí en desbordamiento de pila, donde podemos votar hasta las versiones más correctas y apropiadas.

¿Fue útil?

Solución

Scala 2,13 finalmente ha apoyado: try with resources mediante el uso de Usando :), Ejemplo :

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

o usando Using.resource evitar Try

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

Puede encontrar más ejemplos de usando doc .

  

Una utilidad para llevar a cabo la gestión automática de recursos. Se puede utilizar para realizar una operación utilizando los recursos, después de lo cual libera los recursos en el orden inverso de su creación.

Otros consejos

href="http://polyglot-window.blogspot.com/2009/03/arm-blocks-in-scala-revisited.html" entrada de blog '

de Chris Hansen ARM en bloques Scala: Revisited' de las conversaciones 03/26/09 acerca de acerca de la diapositiva 21 de la de Martin Odersky FOSDEM presentación. Este bloque siguiente se toma directamente de corredera 21 (con permiso):

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

- Fin de la cita -

A continuación, podemos llamar así:

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

¿Cuáles son los inconvenientes de este enfoque? Ese patrón parece hacer frente a 95% de dónde iba a necesitar la gestión automática de recursos ...

Editar: añadido fragmento de código


Edit2: extender el patrón de diseño - que se inspira en la declaración pitón with y dirección:

  • sentencias que se ejecutarán antes del bloque
  • excepción en función de re-lanzar en el recurso gestionado
  • manejar dos recursos con una sola instrucción using
  • manejo específico de recursos proporcionando una conversión implícita y una clase Managed

Este es 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,

He desplegado recientemente la biblioteca scala-brazo para la gestión automática de recursos. Puede encontrar la documentación aquí: https://github.com/jsuereth/scala-arm/wiki

Esta biblioteca es compatible con tres estilos de uso (actualmente):

1) Imperativo / para-expresión:

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

2) monádica de estilo

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) delimitada continuaciones de estilo

Esto es un "eco" del servidor 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()
    }
  }
}

El código hace uso de un tipo de rasgo de recursos, por lo que es capaz de adaptarse a la mayoría de tipos de recursos. Tiene una reserva para utilizar la tipificación estructural contra las clases, ya sea con un método de cerrar o dispose. Por favor, echa un vistazo a la documentación y quiero saber si se piensa en las características útiles que añadir.

Esto es solución James Iry usando continuaciones:

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

Estas son las soluciones con y sin continuaciones para la comparación:

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
}

Y aquí está la sugerencia de mejora 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
}

Veo una evolución gradual de 4 pasos para hacer ARM en Scala:

  1. No ARM: Arena
  2. Sólo los cierres: mejor, pero varios bloques anidados
  3. Continuación Mónada: uso para aplanar la anidación, pero la separación no natural en 2 bloques
  4. continuaciones estilo directo: Nirava, ea! Esta es también la alternativa más de tipo seguro:. Un recurso fuera de bloque conresource será error de tipo

No es de peso ligero (10 líneas de código) Brazo incluido con mejores-archivos. Ver: 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

Aquí es cómo se implementa si no desea que toda la biblioteca:

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

¿Cómo sobre el uso de las clases de 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) } 
}

Otra alternativa es mónada Lazy TryClose de entrecortado. Es bastante bueno con conexiones de bases de datos:

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
}

Y con secuencias:

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
}

Más información aquí: https://github.com/choppythelumberjack/tryclose

Aquí está la respuesta de @ chengpohi, modificado por lo que funciona con Scala 2.8+, en lugar de sólo 2,13 Scala (sí, funciona con 2.13 Scala también):

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
  }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top