2010-02-05 9 views
92

He visto muchos ejemplos de ARM (gestión automática de recursos) en la web para Scala. Parece ser un rito de pasaje para escribir uno, aunque la mayoría se parece mucho el uno al otro. I hizo ver un buen ejemplo usando continuaciones, sin embargo.¿Qué alternativas de gestión automática de recursos existen para Scala?

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

+0

¿Sería esta pregunta generar más respuestas si no fuera un wiki de la comunidad? Tenga en cuenta que si las respuestas votadas en la reputación del premio wiki comunitario ... – huynhjl

+2

, las referencias únicas pueden agregar otro nivel de seguridad a ARM para garantizar que las referencias a los recursos se devuelvan al administrador antes de llamar a close(). http://thread.gmane.org/gmane.comp.lang.scala/19160/focus=19168 – retronym

+0

@retronym Creo que el plugin de singularidad será toda una revolución, más que continuaciones.Y, de hecho, creo que esto es una cosa en Scala que es bastante probable que se transmita a otros idiomas en un futuro no muy lejano. Cuando esto salga, asegurémonos de editar las respuestas en consecuencia. :-) –

Respuesta

57

Daniel,

Recientemente he implementado la biblioteca scala-arm para la administración automática de recursos. Puede encontrar la documentación aquí: http://wiki.github.com/jsuereth/scala-arm/

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) Delimitado Continuaciones-estilo

Aquí es un servidor de "eco" 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 alternativa para usar el tipado estructural en contra de las clases con un método de cierre o disposición. Por favor revisa la documentación y avísame si piensas en alguna característica útil para agregar.

+1

Sí, vi esto. Quiero ver el código para ver cómo logras algunas cosas, pero estoy demasiado ocupado ahora mismo. De todos modos, dado que el objetivo de la pregunta es proporcionar una referencia al código ARM confiable, estoy haciendo de esto la respuesta aceptada. –

8

Daniel, bien, preguntaste esto. Estoy intrigado después de ver el código de James Iry. Veo una evolución gradual de 4 pasos para hacer ARM en Scala:

  1. Sin ARM: Suciedad
  2. Solamente los cierres: mejor, pero varios bloques anidados
  3. Continuación Mónada: uso para aplanar la anidación, pero no naturales separación en 2 bloques
  4. Continuaciones de estilo directo: Nirava, aha! Esta es también la alternativa más segura para el tipo de letra: un recurso externo al bloque Recurso será de tipo error.

Lo que realmente me gustaría ver es una presentación que describa esto. Será muy educativo y debería convencer a los mendigos de que hay un mundo más allá de las Mónadas :)

+1

Eso sí, las CPS en Scala se implementan a través de mónadas. :-) –

+1

Mushtaq, 3) Puede gestionar los recursos en una mónada que no es la mónada de continuación 4) La gestión de recursos utilizando mi código de continuación delimitado por recursos/recursos no es más (y no menos) seguro que "usar". Todavía es posible olvidarse de administrar un recurso que lo necesita. comparar usando (nuevo Recurso()) {primero => val segundo = nuevo Recurso() // ¡Uy! // use resources } // solo se cierra por primera vez withResources { val first = resource (new Resource()) val second = new Resource() // ¡Uy! // use resources ... } // solo se cierra por primera vez –

+2

Daniel, CPS en Scala es como CPS en cualquier lenguaje funcional. Son continuaciones delimitadas que usan una mónada. –

69

Chris Hansen's blog entry 'ARM Blocks in Scala: Revisited' from 3/26/09 habla sobre la diapositiva 21 de Martin Odersky's FOSDEM presentation. Este bloque siguiente se toma directamente de la diapositiva 21 (con permiso):

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

--end quote--

Entonces 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ñade fragmento de código


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

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

Esta es la 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(_) 
    } 
    } 
} 
+2

Hay alternativas, pero no pretendo dar a entender que haya algún problema con eso. Solo quiero todas esas respuestas aquí, en Stack Overflow. :-) –

+4

¿Sabes si hay algo como esto en la API estándar? Parece un deber tener que escribir esto para mí todo el tiempo. –

+0

Ha pasado un tiempo desde que se publicó esta publicación, pero la primera solución no cierra la secuencia interna si se lanza el constructor de salida, lo que probablemente no ocurra aquí, pero hay otros casos en que esto puede ser malo. El cierre también puede arrojar. No hay distinción entre las excepciones fatales tampoco. El segundo tiene olor a código en todas partes y tiene cero ventajas sobre el primero. Incluso pierdes los tipos reales, por lo que sería inútil para algo como un ZipInputStream. – Steiny

17

Aquí es James Iry solución 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} 

Aquí 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 Tiark Rompf de mejora:

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 
} 
+0

¿No tiene problemas el uso de (new BufferedWriter (new FileWriter ("test_copy.txt"))) cuando el constructor BufferedWriter falla? cada recurso debe estar envuelto en un bloque de uso ... – Jaap

+0

@Jaap Este es el estilo [sugerido por Oracle] (http://docs.oracle.com/javase/7/docs/technotes/guides/language/try-with -resources.html). 'BufferedWriter' no arroja excepciones comprobadas, por lo que si se lanza alguna excepción, no se espera que el programa se recupere de ella. –

+0

Eso suena bien, me pareció extraño. – Jaap

5

Hay ARM ligero (10 líneas de código) 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() 
     } 
    } 
    } 
+0

Esto es bastante agradable. Tomé algo similar a este enfoque, pero definí un método 'map' y' flatMap' para CloseableOps en lugar de foreach, por lo que para las comprensiones no se obtendría un trasable. – EdgeCaseBerg

1

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

Otra alternativa es perezoso de entrecortado TryClose mónada 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 corrientes:

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

Cuestiones relacionadas