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.

Foi útil?

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:

  1. Sem braço: sujeira
  2. Apenas fechamentos: melhor, mas vários blocos aninhados
  3. Mônada de continuação: use para achatar o ninho, mas separação não natural em 2 blocos
  4. 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
  }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top