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


102

Я бачив багато прикладів ARM (автоматичного управління ресурсами) в Інтернеті для Scala. Здається, це був обряд проходження одного, хоча більшість схожий приблизно один на одного. Я зробив побачити досить прохолодний приклад використання продовжень, хоча.

У будь-якому випадку, багато цього коду має недоліки того чи іншого типу, тому я вважав, що було б гарною ідеєю мати тут посилання на стек переповнення, де ми можемо проголосувати за найправильніші та відповідні версії.


Чи дасть це запитання більше відповідей, якби не вікі спільноти? Зауважте, якщо відповіді проголосували в репутації вікі-спільноти громади ...
huynhjl

2
унікальні посилання можуть додати ще один рівень безпеки до ARM, щоб гарантувати повернення посилань на ресурси менеджеру до виклику close (). thread.gmane.org/gmane.comp.lang.scala/19160/focus=19168
ретронім

@retronym Я думаю, що плагін унікальності буде досить революцією, більше ніж продовження. Насправді, я думаю, що це одна річ у Scala, яка, ймовірно, виявиться перенесеною на інші мови в не надто віддаленому майбутньому. Коли це з’явиться, давайте обов’язково відредагуйте відповіді відповідно. :-)
Даніель С. Собрал

1
Оскільки мені потрібно вміти вкладати декілька екземплярів java.lang.AutoCloseable, кожен з яких залежить від попереднього успішного інстанції, я нарешті потрапив на дуже корисну для мене модель. Я написав його в якості відповіді на аналогічне питання StackOverflow: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

Відповіді:


10

Наразі Scala 2.13 нарешті підтримує: try with resourcesза допомогою використання :), Приклад:

val lines: Try[Seq[String]] =
  Using(new BufferedReader(new FileReader("file.txt"))) { reader =>
    Iterator.unfold(())(_ => Option(reader.readLine()).map(_ -> ())).toList
  }

або використовуючи Using.resourceAvoidTry

val lines: Seq[String] =
  Using.resource(new BufferedReader(new FileReader("file.txt"))) { reader =>
    Iterator.unfold(())(_ => Option(reader.readLine()).map(_ -> ())).toList
  }

Ви можете знайти більше прикладів із використання doc.

Утиліта для автоматичного управління ресурсами. Він може бути використаний для виконання операції з використанням ресурсів, після чого вивільняє ресурси в зворотному порядку їх створення.


Чи можете ви також додати Using.resourceваріант?
Даніель К. Собрал

@ DanielC.Sobral, звичайно, просто додав його.
Ченпохі

Як би ви написали це для Scala 2.12? Ось подібний usingметод:def using[A <: AutoCloseable, B](resource: A) (block: A => B): B = try block(resource) finally resource.close()
Майк Слінн

75

Запис Кріса Хансена «Блоки ARM у Scala: Переглянуті» від 26.02.99 розповідає про слайд 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: розширення шаблону дизайну - отримання натхнення від withзаяви python та адреси:

  • оператори, що запускаються перед блоком
  • повторне кидання винятку залежно від керованого ресурсу
  • обробка двох ресурсів одним єдиним використанням оператора
  • обробка, що стосується ресурсів, шляхом надання неявного перетворення та Managedкласу

Це з 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
Є альтернативи, але я не маю на увазі, що в цьому щось не так. Я просто хочу, щоб усі ці відповіді були тут, на Stack Overflow. :-)
Даніель К. Собрал

5
Чи знаєте ви, чи є щось подібне у стандартному API? Здається, за клопотом потрібно постійно писати це для себе.
Даніель Дарабос

Пройшло деякий час, оскільки це було розміщено, але перше рішення не закриває внутрішній потік, якщо конструктор викине, що, мабуть, тут не відбудеться, але є й інші випадки, коли це може бути погано. Близький також може кинути. Немає також різниці між смертельними винятками. Другий має кодовий запах скрізь і має нульові переваги перед першим. Ви навіть втрачаєте фактичні типи, тому було б марно для чогось типу ZipInputStream.
steinybot

Як ви рекомендуєте це зробити, якщо блок повертає ітератор?
Хорхе Мачадо

62

Даніель,

Я нещодавно розгорнув бібліотеку масштабування для автоматичного управління ресурсами. Документацію ви можете знайти тут: https://github.com/jsuereth/scala-arm/wiki

Ця бібліотека підтримує три стилі використання (зараз):

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) Розмежований стиль продовження

Ось "echo" 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()
    }
  }
}

Код використовує рису типу ресурсу, тому він може адаптуватися до більшості типів ресурсів. Це резервне використання структурного набору тексту для класів або методом закриття або розпорядження. Будь ласка, ознайомтеся з документацією та повідомте мені, чи не хочете ви додати будь-які зручні функції.


1
Так, я це бачив. Я хочу переглянути код, щоб побачити, як ви виконуєте деякі речі, але я зараз занадто зайнятий. У будь-якому випадку, оскільки метою питання є надання посилання на надійний код ARM, я роблю це прийнятою відповіддю.
Даніель К. Собрал

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
}

Не використовує (новий BufferedWriter (новий FileWriter ("test_copy.txt")), які не зазнають проблем, коли конструктор BufferedWriter виходить з ладу? кожен ресурс повинен бути загорнутий у блок використання ...
Jaap

@Jaap Це стиль, запропонований Oracle . BufferedWriterне викидає перевірені винятки, тому, якщо викинуто якесь виключення, програма не очікує відновлення після цього.
Даніель К. Собрал

7

Я бачу поступову еволюцію в 4 кроки для створення ARM у Scala:

  1. Ніякої зброї: Бруд
  2. Тільки закриття: кращі, але кілька вкладених блоків
  3. Продовження Монада: Використовуйте для згладжування гніздового, але неприродного поділу у 2 блоки
  4. Пряме продовження стилю: Нірава, ага! Це також найбезпечніша альтернатива типу: ресурс поза блоком Resource буде помилкою типу.

1
Зауважте, CPS у Scala реалізовані через монади. :-)
Даніель К. Собрал

1
Mushtaq, 3) Ви можете керувати ресурсами в монаді, яка не є монадою продовження. Ще можна забути керувати ресурсом, який йому потрібен. порівняти за допомогою (new Resource ()) {first => val second = new Resource () // oops! // використовувати ресурси} // тільки перший закривається зResources {val first = resource (new Resource ()) val second = new Resource () // oops! // використовувати ресурси ...} // тільки перший закривається
Джеймс Ірі

2
Даніель, CPS у Scala - це як CPS на будь-якій функціональній мові. Це обмежені продовження, які використовують монаду.
Джеймс Ірі

Джеймс, дякую, що це добре пояснив. Сидячи в Індії, я міг би лише побажати, щоб я був там для вашої бесіди. Чекаю, коли ви розмістите ці слайди в Інтернеті :)
Mushtaq Ahmed

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

Це досить приємно. Я взяв щось подібне до цього підходу, але визначив a mapі flatMapметод для CloseableOps замість того, щоб передбачати, щоб для розуміння не було результату проходження.
EdgeCaseBerg

1

Як щодо використання класів Type

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

Ще одна альтернатива - монаха Choppy's 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


0

Ось відповідь @ chengpohi, модифікована, щоб вона працювала зі Scala 2.8+, замість просто Scala 2.13 (так, вона працює і зі Scala 2.13):

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
  }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.