Какие альтернативы автоматического управления ресурсами существуют для Scala?

102

Я видел много примеров ARM (автоматическое управление ресурсами) в Интернете для Scala. Кажется, что это один из ритуалов, чтобы написать один, хотя большинство из них очень похожи друг на друга. Тем не менее, я видел довольно классный пример с продолжением.

Во всяком случае, многие из этого кода имеют недостатки того или иного типа, поэтому я решил, что было бы неплохо иметь ссылку здесь на Stack Overflow, где мы можем проголосовать за наиболее правильные и соответствующие версии.

  • 0
    Будет ли этот вопрос генерировать больше ответов, если это не вики сообщества? Обратите внимание, если проголосовали ответы в награду репутации сообщества вики ...
  • 2
    уникальные ссылки могут добавить ARM еще один уровень безопасности, чтобы гарантировать, что ссылки на ресурсы будут возвращены менеджеру до вызова close (). thread.gmane.org/gmane.comp.lang.scala/19160/focus=19168
Показать ещё 2 комментария
Теги:

7 ответов

57
Лучший ответ

Даниил,

Недавно я развернул библиотеку scala -arm для автоматического управления ресурсами. Вы можете найти документацию здесь: http://wiki.github.com/jsuereth/scala-arm/

Эта библиотека поддерживает три стиля использования (в настоящее время):

1) Императивный/для выражения:

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

2) Монадический стиль

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) Стиль с разделителями Continuations

Здесь 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()
    }
  }
}

В коде используется использование свойства типа ресурса, поэтому он может адаптироваться к большинству типов ресурсов. Он имеет отказ использовать структурную типизацию для классов с помощью метода close или dispose. Пожалуйста, ознакомьтесь с документацией и сообщите мне, если вы думаете о каких-либо удобных функциях для добавления.

  • 1
    Да, я видел это. Я хочу просмотреть код, чтобы увидеть, как вы выполняете некоторые вещи, но я сейчас слишком занят. В любом случае, поскольку цель вопроса - дать ссылку на надежный код ARM, я делаю это принятым ответом.
72

Chris Hansen запись в блоге 'Блоки ARM в Scala: Revisited' от 26.03.09 рассказывает о слайде 21 Мартина Одерского презентация FOSDEM. Следующий блок берется прямо со слайда 21 (с разрешения):

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

- конец цитаты -

Тогда мы можем так называть:

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

Каковы недостатки такого подхода? Эта модель, по-видимому, адресует 95% того, где мне понадобится автоматическое управление ресурсами...

Изменить: добавлен фрагмент кода


Edit2: расширение шаблона проектирования - вдохновение из инструкции python with и адресации:

  • для выполнения перед блоком
  • исключение повторного броска в зависимости от управляемого ресурса
  • обработка двух ресурсов с помощью одного оператора using
  • обработка ресурсов с помощью неявного преобразования и Managed class

Это с 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
    Есть альтернативы, но я не хотел сказать, что с этим что-то не так. Я просто хочу получить ответы на эти вопросы здесь, на Переполнении стека. :-)
  • 5
    Вы знаете, есть ли что-то подобное в стандартном API? Похоже, рутина, чтобы написать это для себя все время.
Показать ещё 1 комментарий
18

Здесь решение Джеймса Ири с продолжением:

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

Вот решения с и без продолжений для сравнения:

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
}

И здесь предложение Тьярка Ромфа об улучшении:

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
    Разве использование (новый BufferedWriter (новый FileWriter ("test_copy.txt"))) не страдает от проблем при сбое конструктора BufferedWriter? каждый ресурс должен быть обернут в блок использования ...
  • 0
    @Jaap Это стиль, предложенный Oracle . BufferedWriter не генерирует проверенные исключения, поэтому, если выдается какое-либо исключение, программа не должна восстанавливаться после него.
Показать ещё 1 комментарий
7

Я вижу постепенную 4-х ступенчатую эволюцию для выполнения ARM в Scala:

  1. Нет руки: грязь
  2. Только замыкания: лучше, но несколько вложенных блоков
  3. Монада продолжения: используйте для выравнивания вложенности, но неестественное разделение на 2 блока
  4. Прямое продолжение стиля: Нирава, ага! Это также наиболее безопасная альтернатива: ресурс вне блока withResource будет ошибкой типа.
  • 1
    Имейте в виду, CPS в Scala реализованы через монады. :-)
  • 1
    Mushtaq, 3) Вы можете осуществлять управление ресурсами в монаде, которая не является монадой продолжения. 4) Управление ресурсами, использующее мой код продолжения withResources / resource-delimisted, не более (и не менее) безопасен, чем «using». Еще можно забыть управлять ресурсом, который нуждается в нем. сравнить используя (new Resource ()) {first => val second = new Resource () // упс! // использовать ресурсы} // закрывается только первый withResources {val first = resource (new Resource ()) val second = new Resource () // упс! // использовать ресурсы ...} // закрывается только первый
Показать ещё 2 комментария
6

Имеется легкая (10 строк кода) ARM, включаемая с улучшенными файлами. См.: 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

Вот как это реализовано, если вы не хотите всю библиотеку:

  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
    Это довольно мило. Я взял нечто похожее на этот подход, но определил метод map и flatMap для CloseableOps вместо foreach, чтобы для понимания не получился обходной путь.
1

Другой альтернативой является монада Choppy Lazy TryClose. Это довольно хорошо с подключениями к базе данных:

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
}

И с потоками:

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
}

Более подробная информация здесь: https://github.com/choppythelumberjack/tryclose

1

Как насчет использования классов типов

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню