¿Qué alternativas de gestión de recursos automáticos existir para Scala?
-
18-09-2019 - |
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.
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
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:
- No ARM: Arena
- Sólo los cierres: mejor, pero varios bloques anidados
- Continuación Mónada: uso para aplanar la anidación, pero la separación no natural en 2 bloques
- 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
}