Search code examples
scalaresource-management

Simple Scala pattern for "using/try-with-resources" (Automatic Resource Management)


C# has using with the IDisposable interface. Java 7+ has identical functionality with try and the AutoCloseable interface. Scala lets you choose your own implementation to this issue.

scala-arm seems to be the popular choice, and is maintained by one of the Typesafe employees. However, it seems very complicated for such a simple behavior. To clarify, the usage instructions are simple, but understanding how all that code is working internally is rather complex.

I just wrote the following super simple ARM solution:

object SimpleARM {
  def apply[T, Q](c: T {def close(): Unit})(f: (T) => Q): Q = {
    try {
      f(c)
    } finally {
      c.close()
    }
  }
}
  • Is there any benefit to something like simple-arm? It seems all the extra complexity should deliver extra benefit.
  • Normally, it is highly preferable to use a public, open source, library that is supported by others for general purpose behavior over using custom code.
  • Can anyone recommend any improvements?
  • Are there any limitations to this simple approach?

Solution

  • Here is my newer simple, understand at a glance, Scala ARM. This fully supports every use case I can think of including multiple resources and yield values. This uses a very simple for comprehension usage syntax:

    class AutoCloseableWrapper[A <: AutoCloseable](protected val c: A) {
      def map[B](f: (A) => B): B = {
        try {
          f(c)
        } finally {
          c.close()
        }
      }
    
      def foreach(f: (A) => Unit): Unit = map(f)
    
      // Not a proper flatMap.
      def flatMap[B](f: (A) => B): B = map(f)
    
      // Hack :)    
      def withFilter(f: (A) => Boolean) = this
    }
    
    object Arm {
      def apply[A <: AutoCloseable](c: A) = new AutoCloseableWrapper(c)
    }
    

    Here's demo use:

    class DemoCloseable(val s: String) extends AutoCloseable {
      var closed = false
      println(s"DemoCloseable create ${s}")
    
      override def close(): Unit = {
        println(s"DemoCloseable close ${s} previously closed=${closed}")
        closed = true
      }
    }
    
    object DemoCloseable {
      def unapply(dc: DemoCloseable): Option[(String)] = Some(dc.s)
    }
    
    object Demo {
      def main(args: Array[String]): Unit = {
        for (v <- Arm(new DemoCloseable("abc"))) {
          println(s"Using closeable ${v.s}")
        }
    
        for (a <- Arm(new DemoCloseable("a123"));
             b <- Arm(new DemoCloseable("b123"));
             c <- Arm(new DemoCloseable("c123"))) {
          println(s"Using multiple resources for comprehension. a.s=${a.s}. b.s=${b.s}. c.s=${c.s}")
        }
    
        val yieldInt = for (v <- Arm(new DemoCloseable("abc"))) yield 123
        println(s"yieldInt = $yieldInt")
    
        val yieldString = for (DemoCloseable(s) <- Arm(new DemoCloseable("abc")); c <- s) yield c
        println(s"yieldString = $yieldString")
    
        println("done")
      }
    }