Tuesday, February 12, 2008

ARM Blocks in Scala

Update: Here's a better approach to Automatic Resource Management in Scala.

Currently, there is a proposal by Joshua Bloch for the addition of Automatic Resource Management Blocks (ARM Blocks) to Java, possibly as soon as version 7. The idea is that Java should take the responsibility of correctly disposing of resources out of the hands of the developer, much like it did with memory management in its initial design. Personally, I think it is a great idea that wouldn't add too much baggage to the language.

After seeing a couple of implementations of this concept in Scala (here and here), I decided to give it a try myself. I think you'll see that Scala is a very flexible language that already facilitates things like ARM Blocks without explicit language support - mostly thanks to functional programming constructs like closures. In this post, I will assume you have some basic to intermediate knowledge of Scala, including a basic knowledge of its function syntax: "(ArgType1, ArgType2, ...) => ReturnType".

We will construct an Arm object (singleton) which will provide a usable syntax for an ARM Block and offer correct resource disposal.

Here is a first pass:


object Arm {
type CloseType = { def close() }

def manage(resource: CloseType)(block: => Unit) = {
try {
block
} finally {
resource.close()
}
}
}


...and an example:


import Arm.manage

//read in a file
val reader = new BufferedReader(new FileReader("test.txt"))
manage(reader) {
var line: String = reader.readLine
while (line != null) {
println(line)
line = reader.readLine
}
}


Arm.manage takes a resource to manage, and a block of code (no-arg function that does not return anything) to execute. The only restriction on the resource is that it has a close() method that does not return anything (implicit return type of Unit). The definition of CloseType is just for readability. This is a form of statically checked "duck typing". Notice, in the example, that the block of code passed into Arm.manage looks seamless - as if manage was a Scala keyword. The only extra bit of clutter that is necessary is the "import Arm.manage" (or Arm._ if you prefer), but I can live with that.

This is simple and does exactly what we need, but it is limited to only one resource. We can do better:


object Arm {
type CloseType = { def close() }

def manage(resources: CloseType*)(block: => Unit) = {
try {
block
} finally {
resources.foreach( resource => {
try {
resource.close()
} catch {
case e: Exception => e.printStackTrace()
}
})
}
}
}


Here we use Scala's syntax for varargs: CloseType*. Passing zero parameters is allowed here because resources.foreach() does not execute if resources is empty, but I could see an argument for requiring at least one param. This version of Arm.manage solves the problem of managing several resources, but introduces a new problem - what to do when the call to close() throws an exception. These difficulties are mentioned in the ARM proposal, with no solution settled upon. I propose using an optional callback, which is easy enough to do in Scala (and a variant of that would probably work in Java too, now that I think about it):


object Arm {
type CloseType = { def close() }

def manage(resources: CloseType*)(block: => Unit)(implicit exceptionHandler: (Exception) => Unit) = {
try {
block
} finally {
resources.foreach( resource => {
try {
resource.close()
} catch {
case e: Exception =>
try {
exceptionHandler(e)
} catch {
case fatal: Throwable => fatal.printStackTrace() //last resort
}
}
})
}
}
}


...with an example:


val reader = new BufferedReader(new FileReader("test.txt"))
val writer = new BufferedWriter(new FileWriter("test_copy.txt"))

//copy a file, with exception handling
manage(reader, writer) {
var line = reader.readLine
while (line != null) {
writer.write(line)
writer.newLine
line = reader.readLine
}
} {e => //handle it}


...and another:


val reader = new BufferedReader(new FileReader("test.txt"))
val writer = new BufferedWriter(new FileWriter("test_copy.txt"))

//copy a file, no exception handler
manage(reader, writer) {
var line = reader.readLine
while (line != null) {
writer.write(line)
writer.newLine
line = reader.readLine
}
}


Here is Arm in its first non-beta version. The exception handler is declared as "implicit", which means that it is not required - as in the second example. The callback has to take an Exception as a parameter, however, which is not as specific as one could hope for. In Scala, this does not present too much of a problem because of pattern matching. The real problem is what to do when the exception handler throws an exception and we need to keep closing the resources. I don't have a good answer for that one, but the "give up and just print a stack trace" approach is not unprecedented. If you don't believe me, just run the following Java code (I think I saw this concept in a Java Puzzler once):


Thread currentThread = Thread.currentThread();
currentThread.getThreadGroup().uncaughtException(currentThread,
new RuntimeException("Oh no!"));
System.out.println("Moving on");


Furthermore, we have no way to associate an exception with the object which was the source of the exception. There is no doubt in my mind that Arm.manage in this form can be improved upon, but it satisfies our goals. It even gives us control over what to do when an exception is thrown during disposal (within limitations).

One alternative to this implementation would be to declare an overridden Arm.manage method for each type of resource that one would like to manage (e.g. java.io.Closeable, java.sql.Statement, java.sql.Connection, etc.). This gives the benefit of dealing with a more specific type of exception in the callback method, and the possibility of resources that have a method other than close() to dispose them. This comes at the cost of a bit of clutter and rigidity since it does not support an arbitrary object with a close() method. It is definitely worth considering, but in the end I think the flexibility of the "duck typing" approach wins out here.

Another technique that can be used with Arm.manage is implicit type conversion (aka views). This could be useful if you would like Arm.manage to call a method other than close(). Of course it won't call a method other than close(), but you can achieve the same effect. For example:


class Disposable {
def dispose(): Boolean = {
println("Disposing")
return true
}
}

abstract class AbstractCloseType {
def close()
}

implicit def disposable2closeable(disposable: Disposable): CloseType = {
new AbstractCloseType() {
def close() = {
disposable.dispose()
}
}
}

val disposable = new Disposable()
manage(disposable) {
//do something
}


I hope I've shown some of the power and flexibility of Scala. Many of you already know this and it's is why you're reading posts about Scala. To the others - this is just scratching the surface of Scala's potential, I believe. Scala is malleable enough to let you do things like creating DSLs or to write simple code that is virtually indistinguishable from Java. All while not having to give up on static typing or the JVM (or CLR for that matter).

I look forward to writing more code in Scala. I don't know if it will be the "Next Big Thing(TM)", but it is certainly refreshing - and that doesn't hurt when it comes to success.

Update: See the follow-up.

12 comments:

  1. You wrote:

    val reader = new BufferedReader(new FileReader("test.txt"))
    val writer = new BufferedWriter(new FileWriter("test_copy.txt"))

    manage(reader, writer) {
    ...
    } ...


    Example: new BufferReader succeeds. new Bufferwriter fails i.e throws an exception. Nobody closes the BufferReader.
    Your code misses it's own purpose!

    ReplyDelete
  2. You could allow manage to return something by adding a type parameter, e.g.

    def manage[A](resource: CloseType)(block: => A)

    ReplyDelete
  3. You've almost reinvented monads. If you solve the problem "helium" pointed out, and extend it along the lines jeff describes, you pretty much have monads.

    ReplyDelete
  4. @helium: Good point. I will have to think more about it, and I will post a comment in answer and update my post. For now, perhaps declaring them lazy is the temporary solution - I haven't tested it out, though. Thank you.

    @jeff: I thought about that, but I couldn't find a compelling reason/use case to support a return type because you can do any assignments/etc. in the block itself. Let me know if you think of a use case for this.

    @sandro: I'm new to functional programming (coming from a mostly Java background), so I know very little about monads, but I'm learning. I have read that they can be created/used in Scala, so if there is a simpler way to do what I was trying without re-inventing monads, please let me know.

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete
  6. Reworded for clarity.

    RE: Initialization - I was actually hoping to skirt the initialization problem because I didn't have a good solution for it, and I still don't. Simply declaring the resources as lazy doesn't do the trick because passing them as an argument means that they must be initialized before the manage method is called. So, we have a catch 22 since we would ideally like to do that bit of work inside the manage method.

    Any ideas?

    If I do think of an elegant solution, I will create a new post for it. The current post will remain as is.

    ReplyDelete
  7. Hi,
    In the scala-user list somebody suggested ManagedResource from the scalax library. I didn't check it out, but apparently it is a monad that deals with these issues. You would use a for comprehension, you don't really need tu understand monads in order to use it (I barely understand them myself).
    C# had "using" for resources right from the start. In Java we still don't have a good solution :(

    ReplyDelete
  8. @Germán: Thanks for the reference - I will be sure to check it out. I just think that, syntactically, for-comprehensions do not look as nice as this style of approach. It is up for debate whether they are more or less intuitive, but they are less intuitive to me.
    Good point about the using keyword.

    I am working on a follow-up post that addresses the initialization concern, and should have it done in the next day or so if it works out.

    ReplyDelete
  9. Have as well a look at the ARM feature in scalax (http://scalax.scalaforge.org/api/scalax/control/ManagedResource.html)

    ReplyDelete
  10. Just FYI, in .NET & Mono, they use the keyword "using" instead of "manage" for this.

    ReplyDelete
  11. Hi dear it's awesome post.
    It's great stuff i really like it.Human Resource Management

    ReplyDelete
  12. I enjoyed every little bit part of it and I will be waiting for the new updates.
    Extenze Reviews

    ReplyDelete