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.