Automatic Resource Management blocks
December 30th, 2007In his (perhaps successful) attempt to sink the BGGA proposal, Captain Bloch send the closuralists packing to Scala island in the Swiss Sea and promised milk(CICE) and honey(ARM) for the rest.
Besides some remaining choices of freedom a ARM block will look somehow like this:
do (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dest)) {
byte[] buf = new byte[1024];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
}
I thought this should be pretty easy to imitate in Scala as well. Ideally I’d like to have
with (fi<- new FileInputStream(src);
fo<- new FileOutputStream(dest)) {
val buf = new Array[byte](1024);
def cp() {
val n = fi.read(buf)
if (n>0) {
fo.write(buf,0,n)
cp
}
}
cp
}
But writing a “with” is not possible - the 2nd idea was to have a with where the resource is “this”:
with (new DbConnection) {
executeUpdate("delete from scratch")
commit()
}
As Jamie Webb pointed out a simple wrapper is easy to write and might do the job:
class ManagedResource[T<:{def close()}] (resource:T) {
def foreach(f : T => Unit) : Unit =
try {
f(resource)
} finally {
resource.close()
}
}
}
and so it is easy to write:
for(pi<-new ManagedResource(new PipedInputStream);
po<-new ManagedResource(new PipedOutputStream(pi))) {
po.write('A');
Console.println((pi.read).asInstanceOf[Char]);
}
So interesting part it: How does this magic work. On Scala island everybody knows swiss-army knives. We usually use “for”. The code above is equivalent to
for(pi<-new ManagedResource(new PipedInputStream)){
for(po<-new ManagedResource(new PipedOutputStream(pi))) {
po.write('A');
Console.println((pi.read).asInstanceOf[Char]);
}
}
and each block (or closure) is passed to foreach:
new ManagedResource(new PipedInputStream).foreach(pi=>
new ManagedResource(new PipedOutputStream(pi)).foreach(po=>{
po.write('A'); //where is po defined?
Console.println((pi.read).asInstanceOf[Char]);
}
)
)
Here you see the for-magic in action. For defines some nice symbols pi and po for us that make writeing the function much easier than wrapping it all up in nested anonymous functions.
As the expansion of for shows we are getting the nest of resources as we would get if we would write everything by hand. Less visible is the cleanup. Note that the argument to each foreach is a closure. This means th code is excecute where it is writen and not evaluated and then passed to foreach. By this the finally block in foreach executes two times:
- After the print
- After po had been closed
So far so simple, but why some might ask can I refer to pi and po as PipedInput/Outputstreams instead of ManagedResource[T]s?
The reason is that “for” is just some compiler magic, what eventually gets executed is the foreach which takes a function T=>Unit (i.e. a funtion with a single argument of type T and return value void). T here is either PipedInputStream or PipedOutputstream and the pi and po are type to accommondate for that.
In practice it is a bit more complicated to define ManagedResource because there are three different possible expansions (via map and flatMap). We investigated as well lazy resource acquisition, but this might open some other wholes, but it is possible to write ManagedResources that aquire the underlying resource iff it is accessed. You opening a ctor to the application developer is too dangerours as inadvertedly an open resource might get passed to the ManagedResource without that it can tell that it got already opened.
Resources with explicit open methods as Josh mentioned in his text would solve this problem easily with no overhead for the application developer. An alternative are Linear Types, but that doesn’t go well together with a language like Scala.
