Scala için hangi Otomatik Kaynak Yönetimi alternatifleri mevcuttur?


102

Scala için web üzerinde birçok ARM (otomatik kaynak yönetimi) örneği gördüm. Çoğu birbirine benzese de, bir tanesini yazmak bir geçit töreni gibi görünüyor. Ben yaptım gerçi, devamlılık kullanarak oldukça iyi bir örneğini görüyoruz.

Her halükarda, bu kodun birçoğunun şu ya da bu türde kusurları vardır, bu yüzden burada, en doğru ve uygun sürümleri oylayabileceğimiz Stack Overflow'da bir referansa sahip olmanın iyi bir fikir olacağını düşündüm.


Bu soru bir topluluk wiki olmasaydı daha fazla yanıt üretir miydi? Topluluk wiki ödülü itibarındaki yanıtlar
oylanıp oylanmadığından

2
benzersiz referanslar, close () çağrılmadan önce kaynaklara yapılan referansların yöneticiye döndürülmesini sağlamak için ARM'ye başka bir güvenlik düzeyi ekleyebilir. thread.gmane.org/gmane.comp.lang.scala/19160/focus=19168
retronym

@retronym Bence benzersizlik eklentisi, devamlardan çok, oldukça devrimci olacak. Ve aslında, bunun Scala'da çok uzak olmayan bir gelecekte kendisini diğer dillere aktarılmış bulması oldukça muhtemel bir şey olduğunu düşünüyorum. Bu ortaya çıktığında, cevapları buna göre düzenleyelim. :-)
Daniel C. Sobral

1
Birden çok java.lang.AutoCloseable örneğini iç içe geçirebilmem gerektiğinden, her biri bir öncekinin başarılı bir şekilde örneklenmesine bağlı, sonunda benim için çok yararlı olan bir kalıba ulaştım. Bunu, benzer StackOverflow sorusuna yanıt olarak yazdım: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

Yanıtlar:


10

Şimdilik Scala 2.13 nihayet desteklemiştir: try with resourceskullanarak kullanarak , :) Örnek:

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

veya Using.resourcekaçınmakTry

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

Doc kullanarak daha fazla örnek bulabilirsiniz .

Otomatik kaynak yönetimi gerçekleştirmek için bir yardımcı program. Kaynakları kullanarak bir işlem gerçekleştirmek için kullanılabilir, ardından kaynakları oluşturuldukları sırada tersine serbest bırakır.


Lütfen Using.resourcevaryantı da ekleyebilir misiniz ?
Daniel C. Sobral

@ DanielC.Sobral, elbette, yeni ekledi.
chengpohi

Bunu Scala 2.12 için nasıl yazarsınız? İşte benzer bir usingyöntem:def using[A <: AutoCloseable, B](resource: A) (block: A => B): B = try block(resource) finally resource.close()
Mike Slinn

75

Chris Hansen'ın 26 Mart 2009 tarihli blog yazısı 'ARM Blocks in Scala: Revisited' , Martin Odersky'nin FOSDEM sunumunun 21. slaytından bahsediyor . Sonraki blok doğrudan 21. slayttan alınmıştır (izinle):

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

- alıntı sonu--

O zaman şöyle arayabiliriz:

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

Bu yaklaşımın sakıncaları nelerdir? Bu model, otomatik kaynak yönetimine ihtiyaç duyduğum yerlerin% 95'ine hitap ediyor gibi görünüyor ...

Düzenleme: kod parçacığı eklendi


Edit2: tasarım modelini genişletmek - python withifadesinden ilham almak ve şunları ele almak:

  • bloktan önce çalıştırılacak ifadeler
  • yönetilen kaynağa bağlı olarak istisnayı yeniden fırlatma
  • Tek bir kullanım ifadesiyle iki kaynağı ele almak
  • örtük bir dönüşüm ve bir Managedsınıf sağlayarak kaynağa özgü işleme

Bu Scala 2.8 ile.

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
Alternatifler var, ama bunda yanlış bir şey olduğunu ima etmek istemiyorum. Tüm bu cevapları burada Stack Overflow'da istiyorum. :-)
Daniel C. Sobral

5
Standart API'de buna benzer bir şey olup olmadığını biliyor musunuz? Bunu her zaman kendim için yazmak zorunda kalmak bir angarya gibi görünüyor.
Daniel Darabos

Bunun gönderilmesinden bu yana bir süre geçti, ancak dışarı kurucu atarsa ​​ilk çözüm iç akışı kapatmaz ki bu muhtemelen burada olmayacaktır, ancak bunun kötü olabileceği başka durumlar da vardır. Yakın da atabilir. Ölümcül istisnalar arasında da ayrım yok. İkincisi, her yerde kod kokusuna sahiptir ve birincisine göre sıfır avantajı vardır. Gerçek türleri bile kaybedersiniz, bu nedenle ZipInputStream gibi bir şey için yararsız olur.
steinybot

Blok bir yineleyici döndürürse bunu nasıl yapmanızı önerirsiniz?
Jorge Machado

62

Daniel,

Kısa bir süre önce, otomatik kaynak yönetimi için ölçeklendirme kolu kitaplığını devreye aldım. Belgeleri burada bulabilirsiniz: https://github.com/jsuereth/scala-arm/wiki

Bu kitaplık, üç kullanım stilini destekler (şu anda):

1) Zorunlu / ifade için:

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

2) Monadik tarzı

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) Sınırlandırılmış Devamlar stili

İşte bir "echo" tcp sunucusu:

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

Kod, bir Kaynak türü özelliğini kullanır, böylece çoğu kaynak türüne uyum sağlayabilir. Bir close veya dispose yöntemiyle sınıflara karşı yapısal yazmayı kullanmak için bir geri dönüşü vardır. Lütfen dokümantasyona bakın ve ekleyebileceğiniz kullanışlı bir özellik olup olmadığını bana bildirin.


1
Evet, bunu gördüm. Bazı şeyleri nasıl başardığınızı görmek için kodu gözden geçirmek istiyorum, ama şu anda çok meşgulüm. Her neyse, sorunun amacı güvenilir ARM koduna referans sağlamak olduğu için, bunu kabul edilen cevap yapıyorum.
Daniel C. Sobral

18

İşte James Iry devamlılık kullanarak çözüm:

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

Karşılaştırma için devamlılık içeren ve içermeyen çözümler:

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
}

Ve işte Tiark Rompf'un iyileştirme önerisi:

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 yapıcısı başarısız olduğunda (new BufferedWriter (yeni FileWriter ("test_copy.txt"))) kullanmak sorun yaşamaz mı? her kaynak bir kullanım bloğuna sarılmalıdır ...
Jaap

@Jaap Bu, Oracle tarafından önerilen stildir . BufferedWriterkontrol edilen istisnaları atmaz, bu nedenle herhangi bir istisna atılırsa, programın ondan kurtarması beklenmez.
Daniel C. Sobral

7

Scala'da ARM yapmak için kademeli 4 adımlı bir evrim görüyorum:

  1. KOL Yok: Kir
  2. Yalnızca kapatmalar: Daha iyi, ancak birden çok iç içe geçmiş blok
  3. Devam Monad: Yuvayı düzleştirmek için For kullanın, ancak 2 blokta doğal olmayan ayırma
  4. Doğrudan stil devamlılıkları: Nirava, aha! Bu aynı zamanda en güvenli alternatiftir: withResource bloğu dışındaki bir kaynak, tür hatası olacaktır.

1
Unutmayın, Scala'daki CPS monadlar aracılığıyla uygulanır. :-)
Daniel C. Sobral

1
Mushtaq, 3) Sürekliliğin monad'ı olmayan bir monadda kaynak yönetimi yapabilirsiniz 4) withResources / kaynakla sınırlandırılmış sürdürme kodumu kullanarak kaynak yönetimi, "kullanmaktan" daha fazla (ve daha az değil) tür güvenli değildir. Buna ihtiyaç duyan bir kaynağı yönetmeyi unutmak hala mümkündür. (new Resource ()) {first => val second = new Resource () // oops! // kaynakları kullanın} // yalnızca ilk önce Kaynaklarla kapatılır {val ilk = kaynak (yeni Kaynak ()) val ikinci = yeni Kaynak () // oops! // kaynakları kullan ...} // yalnızca ilk kapanır
James Iry

2
Daniel, Scala'daki CPS, herhangi bir işlevsel dilde CPS gibidir. Bir monad kullanan sınırlandırılmış devamlardır.
James Iry

James, bunu iyi açıkladığın için teşekkürler. Hindistan'da otururken keşke BASE konuşmanız için orada olsaydım. Bu slaytları çevrimiçine koyduğunuzda görmeyi bekliyorum :)
Mushtaq Ahmed

6

Daha iyi dosyalara dahil olan hafif (10 satır kod) ARM vardır. Bakınız: 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

Tüm kitaplığı istemiyorsanız, işte şu şekilde uygulanır:

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

Bu oldukça güzel. Bu yaklaşıma benzer bir şey aldım, ancak kavrayışlar için geçilebilir bir sonuç vermemesi için her biri yerine CloseableOps için bir mapve flatMapyöntem tanımladım .
EdgeCaseBerg

1

Tür sınıflarını kullanmaya ne dersiniz?

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

Diğer bir alternatif ise Choppy'nin Lazy TryClose monad'idir. Veritabanı bağlantılarında oldukça iyidir:

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
}

Ve akışlarla:

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
}

Daha fazla bilgi burada: https://github.com/choppythelumberjack/tryclose


0

İşte @ chengpohi'nin cevabı, yalnızca Scala 2.13 yerine Scala 2.8+ ile çalışacak şekilde değiştirildi (evet, Scala 2.13 ile de çalışıyor):

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
  }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.