Какие существуют альтернативы автоматического управления ресурсами для Scala?
Я видел много примеров ARM (автоматическое управление ресурсами) в интернете для Scala. Кажется, это обряд посвящения, чтобы написать один, хотя большинство из них очень похожи друг на друга. Я сделал см. довольно крутой пример использования продолжений.
во всяком случае, многие из этого кода имеют недостатки того или иного типа, поэтому я решил, что было бы неплохо иметь ссылку здесь на Stack Overflow, где мы можем проголосовать за наиболее правильный и подходящий версии.
7 ответов:
Даниила,
Я только недавно развернул библиотеку 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) разделенные продолжения-стиль
вот" Эхо " 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() } } }
код использует тип ресурса-признак, поэтому он может адаптироваться к большинству типов ресурсов. Он имеет резерв для использования структурной типизации против классов с помощью метода close или dispose. Пожалуйста, ознакомьтесь с документацией и дайте мне знать, если вы думаете о каких-либо полезных функций добавить.
Криса Хансена запись в блоге "ARM Blocks in Scala: Revisited" от 3/26/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% того, где мне нужен автоматический ресурс управление...
Edit: добавил фрагмент кода
Edit2: расширение Шаблона дизайна - вдохновение от 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(_) } } }
здесь Джеймс Ирий решение с использованием продолжений:
// 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 }
Даниэль, хорошо, что ты спросил об этом. Я сам заинтригован, увидев код Джеймса Айри. Я вижу постепенную эволюцию 4 шага для выполнения ARM в Scala:
- нет руки: грязь
- только закрытие: лучше, но несколько вложенных блоков
- продолжение монады: используйте для сглаживания вложенности, но неестественного разделения на 2 блока
- прямые продолжения стиля: Нирава, ага! Это тоже самое типа-безопасный вариант: ресурс вне withResource блок будет ошибка типа.
то, что я действительно хотел бы увидеть, - это презентация, описывающая их. Это будет очень познавательно и должно убедить родившихся, что есть мир за пределами монад:)
там облегченная (10 строк кода) рука включенная с лучше-файлами. Смотрите: 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) } }
другая альтернатива-ленивая монада 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