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

StackOverflow https://stackoverflow.com/questions/2207425

  •  18-09-2019
  •  | 
  •  

Вопрос

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

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

Это было полезно?

Решение

На данный момент Скала 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.resource избегать Try

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

Дополнительные примеры вы можете найти в С использованием док.

Утилита для выполнения автоматического управления ресурсами.Его можно использовать для выполнения операции с использованием ресурсов, после чего он освобождает ресурсы в порядке, обратном их созданию.

Другие советы

Криса Хансена запись в блоге «Блоки ARM в Scala:Пересмотрено от 26.03.09. рассказывает о слайде 21 Мартина Одерски Презентация ФОСДЕМ.Следующий блок взят прямо со слайда 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% случаев, когда мне понадобится автоматическое управление ресурсами...

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


Редактировать2: расширение шаблона проектирования — черпаем вдохновение из Python with заявление и обращение:

  • операторы, выполняемые перед блоком
  • повторное выбрасывание исключения в зависимости от управляемого ресурса
  • обработка двух ресурсов с помощью одного оператора using
  • специфичную для ресурса обработку, обеспечивая неявное преобразование и 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(_) 
    }
  }
}

Дэниел,

Совсем недавно я развернул библиотеку scala-arm для автоматического управления ресурсами.Вы можете найти документацию здесь: 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) Стиль продолжений с разделителями

Вот 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()
    }
  }
}

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

Вот Джеймс Айри решение с использованием продолжений:

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

Я вижу постепенную четырехэтапную эволюцию использования ARM в Scala:

  1. Нет ARM:Грязь
  2. Только замыкания:Лучше, но несколько вложенных блоков
  3. Продолжение Монады:Используйте For, чтобы сгладить вложение, но неестественное разделение на 2 блока.
  4. Прямые продолжения стиля:Нирава, ага!Это также наиболее типобезопасная альтернатива:ресурс вне блока withResource будет иметь ошибку типа.

В состав лучших файлов включен легкий (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()
      }
    }
  }

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

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

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

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

Вот ответ @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
  }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top