20

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?
user2864740
  • 60,010
  • 15
  • 145
  • 220
clay
  • 18,138
  • 28
  • 107
  • 192
  • AFAIK, the type of "c" is depending on Reflection, which may be problematic in terms of performance and when using Refactoring or Bytecode obfuscation. Instead, I would simply reuse the type java.lang.AutoCloseable here. – Christian Schlichtherle Dec 02 '14 at 13:02
  • Your code doesn't handle `c` == null case. And it's unclear which exception will be thrown if close() throws an exception too. – Dzmitry Lazerka Oct 21 '15 at 08:35
  • Because I need to be able to nest multiple java.lang.AutoCloseable instances, each of which depends upon the prior one successfully instantiating, I finally hit upon a pattern that has been very useful for me. I wrote it up as an answer on similar StackOverflow question: stackoverflow.com/a/34277491/501113 – chaotic3quilibrium May 27 '17 at 01:28
  • @chaotic3quilibrium, my answer below that contains a super simple Arm system supports the type of nesting you describe. – clay Jun 01 '17 at 18:12
  • Oops. Here's a clickable link to my answer (on a similar and related question): https://stackoverflow.com/a/34277491/501113 – chaotic3quilibrium Jun 03 '17 at 17:56

9 Answers9

10

Your approach with a single simple loan pattern is working fine as long as you don't need to work with several resources, all needing to be managed. That's allowed with scala-arm monadic approach.

import resource.managed

managed(openResA).and(managed(openResB)) acquireFor { (a, b) => ??? }

val res = for {
  a <- managed(openResA)
  b <- managed(openResB)
  c <- managed(openResC)
} yield (a, b, c)

res acquireAndGet {
  case (a, b, c) => ???
}

Main functions to know in scala-arm is resource.managed and .acquired{For,AndGet}, not really complex btw.

Chris Martin
  • 30,334
  • 10
  • 78
  • 137
cchantep
  • 9,118
  • 3
  • 30
  • 41
  • 3
    ARM also mentioned at http://stackoverflow.com/questions/8865754/ But while the project was somewhat revitalized, whether is it reliable enough to be included in one's projects is not clear. So many years in "incubator" status rather than merged into RTL... – Arioch 'The Jun 01 '15 at 08:55
  • There is an important bug specifically about for comprehensions: https://github.com/jsuereth/scala-arm/issues/49 – leventov Dec 06 '19 at 10:37
6

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")
  }
}
clay
  • 18,138
  • 28
  • 107
  • 192
  • Your solution is way too much of an OOP hack, specifically the AutoCloseableWrapper. The solution I provide is far more aligned with the Scala fundamental goals of FP and immutability while retaining the core benefits of the Java ARM model all in a single function with two (currying) parameter lists: https://stackoverflow.com/a/34277491/501113 – chaotic3quilibrium Jun 01 '17 at 18:50
3

This is the code I use:

def use[A <: { def close(): Unit }, B](resource: A)(code: A ⇒ B): B =
    try
        code(resource)
    finally
        resource.close()

Unlike Java try-with-resources, the resource doesn't need to implement AutoCloseable. Only a close() method is needed. It only supports one resource.

Here is an example use with an InputStream:

val path = Paths get "/etc/myfile"
use(Files.newInputStream(path)) { inputStream ⇒
    val firstByte = inputStream.read()
    ....
}
david.perez
  • 6,090
  • 4
  • 34
  • 57
1

http://illegalexception.schlichtherle.de/2012/07/19/try-with-resources-for-scala/

Another implementation, probably more clean from "follow Java specifications" viewpoint, but also fails to support multiple resources

Arioch 'The
  • 15,799
  • 35
  • 62
1

this one works for me really well:

  implicit class ManagedCloseable[C <: AutoCloseable](resource: C) {
    def apply[T](block: (C) => T): T = {
    try {
      block(resource)
    } finally {
      resource.close()
    }
  }

using it for example in this Apache Cassandra client code:

val metadata = Cluster.builder().addContactPoint("vader").withPort(1234).build() { cluster =>
  cluster.getMetadata
}

or even shorter:

val metadata = Cluster.builder().addContactPoint("sedev01").withPort(9999).build()(_.getMetadata)
Peter Ertl
  • 209
  • 2
  • 5
0

An improvement I can recommend to the approach you suggested, which is:

  def autoClose[A <: AutoCloseable, B](resource: A)(code: A ⇒ B): B = {
    try
      code(resource)
    finally
      resource.close()
  }

Is to use:

  def autoClose[A <: AutoCloseable, B](resource: A)(code: A ⇒ B): Try[B] = {
    val tryResult = Try {code(resource)}
    resource.close()
    tryResult
  }

IMHO having the tryResult which is an Try[B], will allow you an easier control flow later.

Johnny
  • 14,397
  • 15
  • 77
  • 118
0

Choppy's Lazy TryClose monad might be what you are looking for (disclosure: I'm the author). It is very similar to Scala's Try but automatically closes resources automatically.

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
}

See here for more info: https://github.com/choppythelumberjack/tryclose

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
0

This is how I'd do it:

  def tryFinally[A, B](acquire: Try[A])(use: A => B)(release: A => Unit): Try[B] =
    for {
      resource <- acquire
      r = Try(use(resource)).fold(
        e => { release(resource); throw e },
        b => { release(resource); b }
      )
    } yield r

you can call .get on it and make it return B.

Maths noob
  • 1,684
  • 20
  • 42
0

Here's one way of doing this in Scala <= 2.12

trait SourceUtils {

  /**
    * Opens a `resource`, passes it to the given function `f`, then
    * closes the resource, returning the value returned from `f`.
    * 
    * Example usage:
    * {{{
    * val myString: String =
    *   using(scala.io.Source.fromFile("file.name")) { source =>
    *     source.getLines.mkString
    *   }
    * }}}
    * 
    * @param resource closeable resource to use, then close
    * @param f function which maps the resource to some return value
    * @tparam T type of the resource to be opened / closed
    * @tparam U type of the return value
    * @return the result of the function `f`
    */
  def using[T <: AutoCloseable, U](resource: => T)(f: T => U): U = {
    scala.util.Try(resource).fold(throw _, source => {
      val result = f(source)
      source.close()
      result
    })
  }
}
awwsmm
  • 1,353
  • 1
  • 18
  • 28