ARM is trying to solve the age old problem of ensuring closure of resources when finished with your business logic. In C++ we would use RRID (Resource Release Is Destruction) to accomplish this. e.g.
template<typename T>
class LockEnforcer : public boost::noncopyable<LockEnforcer>{
T& lock;
public:
LockEnforcer(T& lock) : lock(lock) { lock.lock(); }
~LockEnforcer() { lock.unlock(); }
}
//Later in a method....
{
LockEnforcer(someLock);
//Do my business logic
}
//Lock is shut down here by nature of Destruction
This suffered from a few problems:
- Destructors should not throw exceptions! (Read Effective C++). This could leave you in a dangerous state potentially if you cared whether or not "close" threw some exception.
- The destruction behavior ALWAYS happens. If you wanted to change whether or not cleanup occurs, you have to put state into the class and some more complex logic in the destructor. You also need to make calls on the class to inform it *not* to do the usually cleanup.
Java steps in lacking destructors, and forces developers to take a new paradigm. Let's look at a typical Java JDBC connection usage paradigm.
Connection conn = null;
try {
conn = getConnection();
//Business Logic
} finally {
try {
if(conn != null) {
conn.close();
}
} catch(SQLException e) {
//Ignore - that way if we already had an exception, we don't lose it on close.
}
}
Now, I have potentially buggy code everywhere I need to use connections. I'm also not able to re-use code easily. Now, the Spring framework truly does solve this problem (see any of the *Template classes), but we'll get to how and why it's useful later. For now, assume we're stuck with pure java (or we're in a domain that isn't covered by Spring yet). Coin proposes the following ARM syntax:
try (Connection conn = getConnection()) {
//Business Logic
}
This would be equivalent to the above block. It is much nicer, and certain helps in the general case of resource management. Using multiple resources would of course imply multiple try blocks.
The problem is that this doesn't solve the more general problem. The general problem is that you have code that is duplicated across several methods. This code needs to run before and after some non-duplicated section. Take for example the following:
try {
if(isNewMessage(msg)) {
handleMessage(msg);
}
sendMessageAcknowledgement(msg); //Note this is *not* in the finally
} catch (Exception e) {
//Do something with exceptions
}
I'm unable to use ARM because I want the final code (before the catch) to be run if the above code passes without exception. The other problem here is that I have 5 methods with this same boiler-plate (except same isNewMessage function), and different handleMessage functions. Typically, Java encourages you to create a base class, with abstract handleMessage. Then you extend classes with different handleMessages. That's pretty much the only way to reuse code here. What if I don't really want other classes?
Well, Scala comes to the rescue (slightly). Scala will let me write the above code using functional composition, and behind the scenes it's desugaring to multiple classes that inherit the "Function" interface.
Note: That the Spring *Template classes take this approach, only with less generic interfaces. Instead of "Function1", it's SqlRowMapper.
Here's an example in scala.
//Helper function
def processMessage(msg : Msg)(handlerFunction : Msg => Unit) = {
try {
if(isNewMessage(msg) {
handlerFunction(msg)
}
sendAcknowledgement(msg)
} catch {
case e => //TODO - Handle
}
}
//My actual method
processMessage(msg) {
msg =>
//Business Logic
}
As you can see, I've removed duplicate code, and without a lot of boilerplate. ARM would not be necessary if Java had closures, and the entire set of problems (not just resource management) revolving around repeated code at start/end of blocks would also be solved. Personally, I would rather just wait for Java to have closures.
7 comments:
I agree. Closures/function-literals are definitely the way forward, and I can hear the arguments in Java 8 now... "We have ARM blocks now, so lets not complicate things by adding closures.". Sigh.
I think I have pretty much given up on being productive in Java as it is now (relative to newer languages like Scala).
Personally I think a typestate-based approach could have more general applicability, but verification for such an approach is still academic as far as I know. Frankly anything is an improvement over the
try { open jdbc; use it}
catch {
try { close; error }
catch { error }
}
pattern that I saw torturing the codebase the last time I worked on a big Java project.
+1
Consider C#'s using statement, not far from ARM.
using (Res r = new Res()) {
...
}
Fine, easy to translate to, say, Java + BGGA. But then using can be used for n resources:
using (var in = new FileInputStream(), br = new BufferedReader(in)) {
...
}
I can think of ways of doing this with Java + BGGA, but they wouldn't be as readable.
Also, the performance of using is easier to predict, and using 'return' inside a using block has simpler internal workings (in BGGA, try { return 5; } catch (Throwable t) { return 10; } will return 10, not 5).
Ricki: Your conclusion about BGGA's handling of return is not correct. Go back and read the specification. It does not specify that a return statement is treated as an exception.
Neal,
You're correct, the spec only mentions exceptions for break and continue, not return. I compiled my example and it returned 5. javap revealed the workings, java.lang.Jump and a field for the return value. Interesting.
Thanks for pointing me back to the spec.
Python has what it calls "context handlers" that work much like what you describe. You say "with expr:" to introduce the block, where "expr" returns a context handler object, and the handler's initialization and cleanup functions are called at the beginning and the end of the block. And since it's Python, you're perfectly fine to throw exceptions anywhere in there; the scope containing the with block will be able to catch it.
Post a Comment