2010-02-05 4 views
92

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

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

+0

бы этот вопрос генерировать больше ответов, если это не было сообщество вики? Обратите внимание, что если голосовые ответы в репутации репутации сообщества wiki ... – huynhjl

+2

уникальные ссылки могут добавить еще один уровень безопасности в ARM, чтобы гарантировать, что ссылки на ресурсы возвращаются менеджеру до вызова функции close(). http://thread.gmane.org/gmane.comp.lang.scala/19160/focus=19168 – retronym

+0

@retronym Я думаю, что плагин уникальности будет довольно революционным, а не продолжением.И, на самом деле, я думаю, что это одно в Scala, которое вполне может оказаться перенесенным на другие языки в недалеком будущем. Когда это выйдет, давайте обязательно отредактируем ответы соответственно. :-) –

ответ

57

Daniel,

Недавно я развернул библиотеку 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) 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) разделители Продолжение стиле

Вот «эхо» 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. Пожалуйста, ознакомьтесь с документацией и сообщите мне, если вы думаете о каких-либо удобных функциях для добавления.

+1

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

8

Даниил, хорошо, что вы это задали. Я сам заинтригован, увидев код Джеймса Ири. Я вижу постепенную эволюцию в 4 шага для выполнения ARM в Scala:

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

Что бы я действительно хотел увидеть - это презентация, описывающая эти. Это будет очень познавательно и должно убедить всех, что есть мир за Монадами :)

+1

Помните, что CPS в Scala реализованы через монады. :-) –

+1

Mushtaq, 3) Вы можете выполнять управление ресурсами в монаде, не являющемся монадой продолжения. 4) Управление ресурсами, используя мой код продолжения с ресурсами/ресурсами, не более (и не менее) типа safe чем «использование». По-прежнему можно забыть управлять нужным им ресурсом. сравнение использование (новый ресурс()) {first => val second = new Resource() // oops! // использование ресурсов } // только сначала закрывается withResources { val first = resource (new Resource()) val second = new Resource() // oops! // использование ресурсов ... } // только закрывается первым –

+2

Daniel, CPS в Scala - это как CPS на любом функциональном языке. Это разграниченные продолжения, в которых используется монада. –

69

blog entry 'ARM Blocks in Scala: Revisited' from 3/26/09 Chris Hansen рассказывает о слайде 21 из Мартина Одерского FOSDEM presentation. Следующий блок берется прямо из ползуна 21 (с разрешения):

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

--end quote--

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

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

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

Edit: добавлен код сниппета


edit2: расширение шаблон дизайна - черпают вдохновение из питона with заявление и решение:

  • заявления для запуска перед блоком
  • повторное бросание исключения в зависимости от управляемого ресурса
  • обработки двух ресурсов с использованием одного заявления
  • ресурсов конкретной обработки, обеспечивая неявное преобразование и 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. :-) –

+4

Знаете ли вы, есть ли что-то подобное в стандартном API? Похоже, что нужно все время писать это для себя. –

+0

Прошло некоторое время с тех пор, как это было опубликовано, но первое решение не закрывает внутренний поток, если выбрал конструктор out, который, вероятно, не произойдет, но есть другие случаи, когда это может быть плохо. Закрытие также может бросить. Не существует различия между фатальными исключениями. Второй код имеет запах кода повсюду и имеет нулевые преимущества по сравнению с первым. Вы даже потеряете фактические типы, поэтому было бы бесполезно для чего-то вроде ZipInputStream. – Steiny

17

Вот James Iry решение с помощью продолжений:

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

А вот предложение 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 
} 
+0

Не использует (новый BufferedWriter (новый FileWriter («test_copy.txt»))) страдает от проблем, когда конструктор BufferedWriter терпит неудачу? каждый ресурс должен быть обернут в блок использования ... – Jaap

+0

@Jaap Это стиль [предложенный Oracle] (http://docs.oracle.com/javase/7/docs/technotes/guides/language/try-with -resources.html). 'BufferedWriter' не генерирует проверенные исключения, поэтому, если какое-либо исключение выбрано, программа не должна восстанавливаться. –

+0

Это звучит правильно, это просто показалось мне странным. – Jaap

5

Есть легкие (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, чтобы для понимания не приводило к обходу. – EdgeCaseBerg

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

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

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

 Смежные вопросы

  • Нет связанных вопросов^_^