Những lựa chọn thay thế Quản lý Tài nguyên Tự động nào tồn tại cho Scala?


102

Tôi đã thấy nhiều ví dụ về ARM (quản lý tài nguyên tự động) trên web cho Scala. Nó có vẻ là một nghi thức của đoạn văn để viết một, mặc dù hầu hết trông khá giống nhau. Tuy nhiên, tôi đã thấy một ví dụ khá thú vị khi sử dụng các phép liên tục.

Dù sao đi nữa, rất nhiều đoạn mã đó có sai sót thuộc loại này hay loại khác, vì vậy tôi thấy rằng sẽ là một ý kiến ​​hay nếu tham khảo ở đây trên Stack Overflow, nơi chúng ta có thể bình chọn phiên bản chính xác và phù hợp nhất.


Câu hỏi này sẽ tạo ra nhiều câu trả lời hơn nếu nó không phải là wiki cộng đồng? Lưu ý chắc chắn nếu câu trả lời được bình chọn trong wiki cộng đồng giải thưởng danh tiếng ...
huynhjl

2
các tham chiếu duy nhất có thể thêm một mức độ an toàn khác cho ARM để đảm bảo rằng các tham chiếu đến tài nguyên được trả lại cho trình quản lý trước khi hàm close () được gọi. thread.gmane.org/gmane.comp.lang.scala/19160/focus=19168
viết lại

@retronym Tôi nghĩ rằng plugin tính duy nhất sẽ là một cuộc cách mạng, hơn là tính liên tục. Và, trên thực tế, tôi nghĩ đây là một điều trong Scala có khả năng được chuyển sang các ngôn ngữ khác trong một tương lai không xa. Khi điều này xuất hiện, chúng ta hãy nhớ chỉnh sửa câu trả lời cho phù hợp. :-)
Daniel C. Sobral

1
Bởi vì tôi cần có thể lồng nhiều phiên bản java.lang.AutoClosable, mỗi phiên bản trong số đó phụ thuộc vào phiên bản trước khi khởi tạo thành công, cuối cùng tôi đã tìm thấy một mẫu rất hữu ích cho tôi. Tôi đã viết nó lên như là một câu trả lời về câu hỏi StackOverflow tương tự: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

Câu trả lời:


10

Hiện tại, Scala 2.13 cuối cùng đã hỗ trợ: try with resourcesbằng cách sử dụng Sử dụng :), Ví dụ:

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

hoặc sử dụng Using.resourcetránhTry

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

Bạn có thể tìm thêm ví dụ từ Sử dụng doc.

Một tiện ích để thực hiện quản lý tài nguyên tự động. Nó có thể được sử dụng để thực hiện một hoạt động sử dụng tài nguyên, sau đó nó giải phóng tài nguyên theo thứ tự ngược lại với việc tạo ra chúng.


Bạn cũng có thể vui lòng thêm Using.resourcebiến thể được không?
Daniel C. Sobral

@ DanielC.Sobral, chắc chắn rồi, vừa mới thêm vào.
chengpohi

Bạn sẽ viết nó như thế nào cho Scala 2.12? Dưới đây là một tương tự usingphương pháp:def using[A <: AutoCloseable, B](resource: A) (block: A => B): B = try block(resource) finally resource.close()
Mike Slinn

75

Mục blog của Chris Hansen 'Khối ARM trong Scala: Được xem lại' từ 26/03/09 nói về slide 21 trong bài thuyết trình FOSDEM của Martin Odersky . Khối tiếp theo này được lấy thẳng từ slide 21 (với sự cho phép):

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

- gửi báo giá--

Sau đó, chúng ta có thể gọi như thế này:

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

Hạn chế của phương pháp này là gì? Mô hình đó dường như sẽ giải quyết 95% nơi tôi cần quản lý tài nguyên tự động ...

Chỉnh sửa: đoạn mã đã thêm


Edit2: mở rộng mẫu thiết kế - lấy cảm hứng từ withcâu lệnh python và giải quyết:

  • câu lệnh chạy trước khối
  • ném lại ngoại lệ tùy thuộc vào tài nguyên được quản lý
  • xử lý hai tài nguyên với một câu lệnh sử dụng duy nhất
  • xử lý tài nguyên cụ thể bằng cách cung cấp một chuyển đổi ngầm định và một Managedlớp

Đây là với 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
Có những lựa chọn thay thế, nhưng tôi không có ý ám chỉ rằng có điều gì đó không ổn với điều đó. Tôi chỉ muốn tất cả những câu trả lời đó ở đây, trên Stack Overflow. :-)
Daniel C. Sobral

5
Bạn có biết nếu có một cái gì đó như thế này trong API tiêu chuẩn? Có vẻ như là một việc vặt khi phải viết cái này cho chính mình mọi lúc.
Daniel Darabos

Đã được một thời gian kể từ khi điều này được đăng nhưng giải pháp đầu tiên không đóng luồng bên trong nếu phương thức khởi tạo ném ra có thể sẽ không xảy ra ở đây nhưng có những trường hợp khác mà điều này có thể tồi tệ. Đóng cũng có thể ném. Không có sự phân biệt giữa các trường hợp ngoại lệ chết người. Cái thứ hai có mùi mã ở khắp mọi nơi và không có lợi thế hơn cái đầu tiên. Bạn thậm chí còn mất các kiểu thực tế, do đó sẽ vô dụng đối với một thứ như ZipInputStream.
steinybot

Bạn khuyên bạn nên thực hiện điều này như thế nào nếu khối trả về một trình lặp?
Jorge Machado

62

Daniel,

Tôi vừa mới triển khai thư viện scala-arm để quản lý tài nguyên tự động. Bạn có thể tìm tài liệu tại đây: https://github.com/jsuereth/scala-arm/wiki

Thư viện này hỗ trợ ba kiểu sử dụng (hiện tại):

1) Mệnh lệnh / biểu thức:

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

2) Phong cách đơn nguyên

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) Kiểu tiếp tục có phân định

Đây là máy chủ tcp "echo":

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

Mã sử ​​dụng đặc điểm loại Tài nguyên, vì vậy nó có thể thích ứng với hầu hết các loại tài nguyên. Nó có một dự phòng để sử dụng cách nhập cấu trúc chống lại các lớp với phương thức đóng hoặc hủy. Vui lòng xem tài liệu và cho tôi biết nếu bạn nghĩ đến bất kỳ tính năng hữu ích nào để thêm vào.


1
Vâng, tôi đã thấy điều này. Tôi muốn xem qua mã, để xem cách bạn hoàn thành một số việc, nhưng hiện tại tôi quá bận. Dù sao, vì mục tiêu của câu hỏi là cung cấp một tham chiếu đến mã ARM đáng tin cậy, tôi đang coi đây là câu trả lời được chấp nhận.
Daniel C. Sobral

18

Đây là giải pháp James Iry sử dụng liên tục:

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

Dưới đây là các giải pháp có và không có liên tục để so sánh:

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
}

Và đây là gợi ý cải tiến của 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
}

Không sử dụng (BufferedWriter mới (FileWriter mới ("test_copy.txt")))) gặp sự cố khi phương thức khởi tạo BufferedWriter bị lỗi? mọi tài nguyên nên được gói trong một khối sử dụng ...
Jaap

@Jaap Đây là kiểu do Oracle gợi ý . BufferedWriterkhông ném các ngoại lệ đã kiểm tra, vì vậy nếu có bất kỳ ngoại lệ nào được ném ra, chương trình sẽ không khôi phục từ đó.
Daniel C. Sobral

7

Tôi thấy quá trình tiến hóa 4 bước dần dần để thực hiện ARM trong Scala:

  1. Không có ARM: Bụi bẩn
  2. Chỉ các khối đóng: Tốt hơn, nhưng có nhiều khối lồng nhau
  3. Đơn nguyên tiếp tục: Sử dụng For để làm phẳng sự phân tách lồng nhau nhưng không tự nhiên trong 2 khối
  4. Phong cách liên tục trực tiếp: Nirava, aha! Đây cũng là giải pháp thay thế an toàn về kiểu nhất: tài nguyên bên ngoài với khối Nguồn sẽ bị lỗi kiểu.

1
Xin lưu ý, CPS trong Scala được thực hiện thông qua các monads. :-)
Daniel C. Sobral

1
Mushtaq, 3) Bạn có thể thực hiện quản lý tài nguyên trong một đơn nguyên không phải là đơn nguyên tiếp tục 4) Quản lý tài nguyên bằng cách sử dụng mã liên tục được phân tách bằng withResources / tài nguyên của tôi không hơn (và không kém) loại an toàn hơn "using". Vẫn có thể quên quản lý một tài nguyên cần nó. so sánh bằng cách sử dụng (new Resource ()) {first => val second = new Resource () // oops! // sử dụng tài nguyên} // chỉ đầu tiên bị đóng vớiResources {val first = resource (new Resource ()) val second = new Resource () // oops! // sử dụng tài nguyên ...} // chỉ đóng lần đầu tiên
James Iry

2
Daniel, CPS trong Scala giống như CPS trong bất kỳ ngôn ngữ chức năng nào. Đó là sự liên tục được phân định sử dụng một đơn nguyên.
James Iry

James, cảm ơn vì đã giải thích nó tốt. Ngồi ở Ấn Độ, tôi chỉ có thể ước rằng tôi ở đó để nói chuyện CƠ BẢN của bạn. Chờ xem khi bạn đưa những trang trình bày đó lên mạng :)
Mushtaq Ahmed 13/02/10

6

Có ARM trọng lượng nhẹ (10 dòng mã) đi kèm với các tệp tốt hơn. Xem: 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

Đây là cách nó được triển khai nếu bạn không muốn toàn bộ thư viện:

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

Điều này là khá tốt đẹp. Tôi đã thực hiện một cái gì đó tương tự như cách tiếp cận này nhưng xác định một mapflatMapphương pháp cho ClosableOps thay vì foreach để việc hiểu sẽ không mang lại khả năng duyệt.
EdgeCaseBerg

1

Làm thế nào về việc sử dụng các lớp Loại

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

Một giải pháp thay thế khác là Choppy's Lazy TryClose monad. Nó khá tốt với các kết nối cơ sở dữ liệu:

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
}

Và với các luồng:

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
}

Thông tin thêm tại đây: https://github.com/choppythelumberjack/tryclose


0

Đây là câu trả lời của @ chengpohi, đã được sửa đổi để nó hoạt động với Scala 2.8+, thay vì chỉ Scala 2.13 (vâng, nó cũng hoạt động với 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
  }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.