Quais alternativas automáticas de gerenciamento de recursos existem para Scala?
-
18-09-2019 - |
Pergunta
Eu já vi muitos exemplos de ARM (Gerenciamento de Recursos Automáticos) na Web para Scala. Parece ser um rito de passagem escrever um, embora a maioria pareça muito uma com a outra. EU fez Veja um exemplo bem legal usando continuações, no entanto.
De qualquer forma, grande parte desse código tem falhas de um tipo ou de outro, então achei que seria uma boa idéia ter uma referência aqui no excesso de pilha, onde podemos votar nas versões mais corretas e apropriadas.
Solução
Por enquanto Scala 2.13 finalmente apoiou: try with resources
usando Usando :), Exemplo:
val lines: Try[Seq[String]] =
Using(new BufferedReader(new FileReader("file.txt"))) { reader =>
Iterator.unfold(())(_ => Option(reader.readLine()).map(_ -> ())).toList
}
ou 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
}
Você pode encontrar mais exemplos de Usando Doc.
Uma utilidade para realizar gerenciamento automático de recursos. Ele pode ser usado para executar uma operação usando recursos, após o que libera os recursos na ordem inversa de sua criação.
Outras dicas
Chris Hansen's Entrada do blog 'Blocos de braço em scala: revisitado' de 26/3/09 fala sobre o slide 21 do Martin Odersky's Apresentação do Fosdem. Este próximo bloco é retirado do slide 21 (com permissão):
def using[T <: { def close() }]
(resource: T)
(block: T => Unit)
{
try {
block(resource)
} finally {
if (resource != null) resource.close()
}
}
-end cotação--
Então podemos ligar assim:
using(new BufferedReader(new FileReader("file"))) { r =>
var count = 0
while (r.readLine != null) count += 1
println(count)
}
Quais são as desvantagens dessa abordagem? Esse padrão parece abordar 95% de onde eu precisaria de gerenciamento automático de recursos ...
Editar: Adicionado snippet de código
Edit2: estendendo o padrão de design - inspirando -se do Python with
declaração e endereço:
- declarações a serem executadas antes do bloco
- Representando a exceção, dependendo do recurso gerenciado
- lidando com dois recursos com um único usando a instrução
- manuseio específico de recursos, fornecendo uma conversão implícita e um
Managed
classe
Isso é com 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,
Acabei de implantar recentemente a Biblioteca Scala-Arm para gerenciamento automático de recursos. Você pode encontrar a documentação aqui: https://github.com/jsuereth/scala-arm/wiki
Esta biblioteca suporta três estilos de uso (atualmente):
1) Imperativo/para a expressão:
import resource._
for(input <- managed(new FileInputStream("test.txt")) {
// Code that uses the input as a FileInputStream
}
2) estilo Monadic
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) estilo de continuação delimitado
Aqui está um servidor 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()
}
}
}
O código utiliza um traço de tipo de recurso, para que seja capaz de se adaptar à maioria dos tipos de recursos. Ele tem um fallback para usar a digitação estrutural contra classes com um método de fechamento ou disposição. Confira a documentação e deixe -me saber se você pensar em algum recurso útil para adicionar.
Aqui está James Iry Solução usando continuações:
// 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}
Aqui estão as soluções com e sem continuações para comparação:
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
}
E aqui está a sugestão de melhoria do 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
}
Eu vejo uma evolução gradual de 4 etapas para fazer braço em Scala:
- Sem braço: sujeira
- Apenas fechamentos: melhor, mas vários blocos aninhados
- Mônada de continuação: use para achatar o ninho, mas separação não natural em 2 blocos
- Continuações de estilo direto: Nirava, Aha! Essa também é a alternativa mais segura de tipo: um recurso fora do bloco WithResource será um erro de tipo.
Há um braço leve (10 linhas de código) incluído com melhores arquivos. 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
Aqui está como é implementado se você não quiser toda a 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()
}
}
}
Que tal usar as classes 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) }
}
Outra alternativa é a preguiçosa Mônada de Tryclose de Choppy. É muito bom com conexões de banco de dados:
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
}
E com fluxos:
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
}
Mais informações aqui: https://github.com/choppythelumberjack/tryclose
Aqui está a resposta de @Chengpohi, modificada para que funcione com o Scala 2.8+, em vez de apenas Scala 2.13 (sim, funciona com o Scala 2.13 também):
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
}