Wednesday, May 14, 2008

Staticly-Duck-Typed

After talking to a coworker about all sorts of neat tricks he does to handle closing resources (using reflection), I had a realization that I should blog about how easy Scala can make this.

First off, we'll discuss scala's type system. Scala allows us to differentiate "type" from "class". A class is something that can be instantiated. A type is more like an interface in java, only a class does not need to implement it. e.g. Below is the code for a "type" closeable. *Any* class that has a close method would "fit" type closeable.

 type MyCloseable = { def close(): Unit }


That's great. Now I have a type of "objects with a close method". (Notice the close method takes no arguments and returns null. Other close methods WILL NOT fit). So... how does this become useful? Let's write a method that will call our close inside a try-catch and log exceptions (using System.err for now).


object CloseUtils {
type MyCloseable = { def close():Unit }

def close(x : MyCloseable) = {
try {
x.close();
} catch {
case _=> //TODO Log to real log
System.err.println("Trouble closing: " + x);
}
}
}


Neato! Now I can call CloseUtils.close(someThingCloseable), and I it will close and make sure we don't have exceptions thrown. Well, that's great, but I think we can do even better. I'm really annoyed with having to remember to close streams when I'm finished. It should just close at the end of the block. Does Scala offer something I can use instead of RRID? Sure does.

The next topic is what Martin Ordesky labeled "Pimp My Libraries" or just implicits. I can define an implicit conversion that the compiler will use to add functionality/methods onto a class. Let me use this with the Closable type to add a "doThenClose" method to closeables. I'm going to define a new class that takes in a closeable as reference, and defines new methods. These methods are then visible on classes of type closeable (duck-typed, remember!) as long as import the implicit functions correctly (import static.class.name._);


object CloseUtils {
type MyCloseable = { def close():Unit }

def close(x : MyCloseable) = {
try {
x.close();
} catch {
case _=> //TODO Log to real log
System.err.println("Trouble closing: " + x);
}
}
/**
* Used to add methods to Closeable types
*/
implicit def pimpCloseable(closeable:MyCloseable) = new PimpedCloseable(closeable);

/**
* Defines new scala-awesome features we want.
*/
class PimpedCloseable(closeable: MyCloseable) {
/**
* Executes a function, then closes the resource.
*/
def doThenClose(f : => Unit) : Unit = {
try {
f
} catch {
case _ => System.err.println("Trouble executing", f);
} finally {
CloseUtils.close(closeable);
}
}
}
}


Ok, so now we're set up, let's make use of our new language feature!

import CloseUtils._;
import java.io._;

var stringWriter = new StringWriter();
stringWriter doThenClose {
for(i <- 1 to 10) {
stringWriter.append("Test safe write " + i + "\n");
}
};
System.out.println("out = " + stringWriter.toString);


Notice how we make no reference to pimpedCloseable or CloseUtils... yet we get the added functionality!

Now, how about returning something from the closure? No problem... time for generics (or templates or whatever scala calls them).


object CloseUtils {
type MyCloseable = { def close():Unit }

def close(x : MyCloseable) = {
try {
x.close();
} catch {
case _=> //TODO Log to real log
System.err.println("Trouble closing: " + x);
}
}
/**
* Used to add methods to Closeable types
*/
implicit def pimpCloseable(closeable:MyCloseable) = new PimpedCloseable(closeable);

/**
* Defines new scala-awesome features we want.
*/
class PimpedCloseable(closeable: MyCloseable) {
/**
* Executes a function, then closes the resource.
*/
def doThenClose[A](f : => A) : Option[A]= {
try {
return Some(f)
} catch {
case _ => System.err.println("Trouble executing", f);
} finally {
CloseUtils.close(closeable);
}
return None;
}
}
}


So the new thing we've done here is use the Option class to have a null-safe return. If f does *not* throw an exception, we return the value it returned. Otherwise we return null. Let's see how a client would use this:


import java.io._;
import CloseUtils._;

object Testing extends Application {

var stringWriter = new StringWriter();
val result = stringWriter doThenClose {
stringWriter.append("Test safe write 2");
stringWriter.toString;
};

// If there is no result, we return the string "Problem reading string!"
System.out.println(result.getOrElse( { "Problem reading string!" }));
}


Notice the getOrElse method. This allows you to provide a default incase the function returned null.


Anyway, hoped you enjoyed my random rant. I definitely learned a lot more about scala writing this sample code (and getting it to compile)

2 comments:

Landei said...

Cool blog!

Wouldn't it be nicer to give the Exception back, e.g. using an Either?

J. Suereth said...

You're right. In fact, I think it would be better to let the exception get thrown, but make sure the resource is still closed. Here's a much better version of doThenClose based on your comment:

def doThenClose[A](f : => A) : A = {
try {
return f
} finally {
CloseUtils.close(closeable);
}
}