What Automatic Resource Management alternatives exist for Scala?

后端 未结 9 1324
半阙折子戏
半阙折子戏 2020-12-02 03:57

I have seen many examples of ARM (automatic resource management) on the web for Scala. It seems to be a rite-of-passage to write one, though most look pretty much like one a

相关标签:
9条回答
  • 2020-12-02 04:18

    Another alternative is Choppy's Lazy TryClose monad. It's pretty good with database connections:

    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
    }
    

    And with streams:

    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
    }
    

    More info here: https://github.com/choppythelumberjack/tryclose

    0 讨论(0)
  • 2020-12-02 04:19

    How about using Type classes

    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) } 
    }
    
    0 讨论(0)
  • 2020-12-02 04:20

    Chris Hansen's blog entry 'ARM Blocks in Scala: Revisited' from 3/26/09 talks about about slide 21 of Martin Odersky's FOSDEM presentation. This next block is taken straight from slide 21 (with permission):

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

    --end quote--

    Then we can call like this:

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

    What are the drawbacks of this approach? That pattern would seem to address 95% of where I would need automatic resource management...

    Edit: added code snippet


    Edit2: extending the design pattern - taking inspiration from python with statement and addressing:

    • statements to run before the block
    • re-throwing exception depending on the managed resource
    • handling two resources with one single using statement
    • resource-specific handling by providing an implicit conversion and a Managed class

    This is with 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(_) 
        }
      }
    }
    
    0 讨论(0)
  • 2020-12-02 04:20

    There is light-weight (10 lines of code) ARM included with better-files. See: 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
    

    Here is how it is implemented if you don't want the whole library:

      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 讨论(0)
  • 2020-12-02 04:20

    Here is @chengpohi's answer, modified so it works with Scala 2.8+, instead of just Scala 2.13 (yes, it works with Scala 2.13 also):

    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
      }
    
    0 讨论(0)
  • 2020-12-02 04:27

    I see a gradual 4 step evolution for doing ARM in Scala:

    1. No ARM: Dirt
    2. Only closures: Better, but multiple nested blocks
    3. Continuation Monad: Use For to flatten the nesting, but unnatural separation in 2 blocks
    4. Direct style continuations: Nirava, aha! This is also the most type-safe alternative: a resource outside withResource block will be type error.
    0 讨论(0)
提交回复
热议问题