Saturday, January 24, 2009

A Functional Approach to Java Managed Resources

Quick, what's your standard idiom for closing Closeable resources in Java? Is it this?

OutputStream stream = new FileOutputStream(file);
try {
writeMessage(stream);
} finally {
stream.close();
}
I promise this isn't about a lack of a try/catch around the close() invocation. I'd rather clutter the prose with a message to ignore it (ignore it) than clutter the code sample. No, this post is about safely closing resources using generics, an anonymous class, and almost twice as many characters... sweet, an orgy of complexity!
withClose(
new FileOutputStream(file),
new Action<OutputStream>(){
public void call(OutputStream stream) throws IOException {
writeMessage(stream);
}
});
Just because Java makes it hard doesn't mean it isn't the right thing to do. Consider this, what stops you from forgetting the close() on a resource? A unit test? A code review? A pairing partner? Or just an amazing attention to detail? Do you think your method is more or less reliable than the javac compiler?

Let's examine what's going on in the above examples. The first one is simple. You create a stream, write some data into it, and then close it. The second does the same thing slightly differently. You create a stream, and then create a little function that writes some data into a stream. And then you pass them both to a method that will presumably call your function back, passing you the stream, and then safely closing it when it's all done.

The first one is simpler. There's no argument about that. But here's why I prefer the second one:
  • Structure over Convention - Uncle Bob Martin's latest book Clean Code recommends using structure over convention. Odd advice in a post-Rails world, but there is value in having the design decision of closing resources in a single place, the withClose method. The point is that there is no way to incorrectly invoke withClose(). You either give it objects of the correct type of the compiler complains.

  • Lean on the Compiler - Michael Feathers coined the term "Lean on the Compiler" in his book, Working Effectively with Legacy Code. I say, if we can get the compiler to enforce something for us then we should. Why use a statically typed language if that isn't a principle? Unfortunately for me, Michael Feathers meant something totally different in this book. He recommended finding usages of a variable by renaming the definitions and then analyzing the resulting compiler errors. Programmers seem to have no problem with co-opting terms and overloading them with meaning. I'm sticking with my new, invented meaning; let the confusion begin now.

  • Dependency Injection - Another Bob Martin book, Agile Software Development, strongly advocates Dependency Injection and the Hollywood Principle. The idea is the movie producer refrain, "Don't call us, we'll call you." Interpreted here to mean, don't call our object to interact with it, give us a piece of code to execute and we'll give you what you need. Like dropping off your headshot at the casting agency, we're dropping off our Action function object at the withClose() service. You could further make this idiom more encapsulated by making the Action function part of the Closeable package and then making the methods on Closeable package-private, thus forcing usage of this idiom.

  • Monady Goodness? - Man, those functional programming guys get all the cool words: catamorphisms, continuations, and monads. While there is certainly more to monads than just inversion of control, the basic usage is, as Brian Hurt says, "this code needs to execute in a time and place where condition X is true." In this case, the time when an OutputStream is open and usable.

The problem with this approach is the verboseness. It's not just creating the anonymous class, it's the fact that Java requires you to define a named function object with explicitly named types (including Exception types!). In this case, Action:
interface Action <T> {
void call(T input) throws IOException;
}
The Functional Java library helps here by providing a unary function called F1, but seriously, now many F1, F2, F3 types do you want in your codebase. I'd have more luck implementing these myself than convincing the gatekeepers to include Functional Java in the release. In fact, I did and no one noticed. Another problem is the generics of a withClose() function can appear daunting. 3 Ts and an extends? Sounds like my shopping list at Target for my daughter.
static <T extends Closeable> void withClose(T stream, Action<T> delegate) throws IOException {
try {
delegate.call(stream);
} finally {
stream.close();
}
}
I've memorized Josh Bloch's PECS (Producer Extends Consumer Super) as a mnemonic for remembering how to write wildcards and I still never write it correctly the first time. Arguably ever. Oh well, write it once and be done with it.

The last issue is combinatorics. How do you combine a withClose() action and a withOpen() action and a withFoo() action... Well, as static method invocations it would be darn hard. Java uses objects as the unit of composition, so you need to reference the withX() monad style method as an object not a global procedure. More objects means more verboseness. And combining them elegantly requires some sort of logical combination library, in the style of bin4j. How far down the functional programming road do you want to take your Java code? A functional language would "fix" all these problems:

  • Function objects do not need to be named or declared

  • The syntax for creating function objects is trivial

  • The generic types can be inferred rather than declared

  • And functions are the natural unit of composition, and object baggage can be discarded

Perhaps it's time to try a functional language?

7 comments:

Robert Fischer said...

Groovy provides a bunch of these styles of structures (e.g. withReader), which are really just extensions of Ruby's IO.open(fd,mode){block} stunt, which is really just Perl's while(<FILEHANDLE>) trick.

And it's probably worth noting that the core .Net languages already have IDisposable and using, which implements this exact pattern.

In short, this isn't a strong case for the necessity of a functional programming language -- it's an idiom easily supported in non-functional languages. The fact that Java doesn't have an equivalent to the C# "using" statement is really a shame on the Java language.

Greg Dennis said...
This comment has been removed by the author.
Greg Dennis said...

I thought the standard idiom was to be prepared for the constructor to throw an exception, and to ignore the exception that close may throw:

OutputStream stream = null;
try {
stream = new FileOutputStream(file);
writeMessage(stream);
} finally {
if (stream != null) {
try { stream.close(); }
catch (IOException ignore) {}
}
}

Hamlet D'Arcy said...

@Robert correct. i would prefer my language to have structures that support me programming this way rather than baked into the language on an ad hoc basis. For instance, I'd prefer to see closures in a language before a "with" keyword that depends on Closeable/IDisposable. I'll defend this as a "functional approach" because you are passing functions around and doing delayed evaluation. You can do this stuff in an OO language but you /think/ like this in a function language. fair?

@pitpat i was just trying to think of an easy, short example to grasp. i think your example is closer to a canonical form.

Robert Fischer said...

@Hamlet

Certainly true. When you get into functional programming, you start to recognize the for loop and the using statement as just chintzy, half-assed monads.

The one thing a functional programming language gives you which an imperative language doesn't is the ability to generate new structures that look like this. Hacking that capability in (in a limited way) is really the idea behind Groovy and Ruby's implied-block-argument structure.

Strangely, there's yet to be a functional language out there which takes the nice syntax of Groovy/Ruby's closures. For some reason, they all have a pretty chatty way of declaring functions inline and/or passing functions as arguments to other functions.

Greg Dennis said...

I brought that up, because if you want the stream constructed within the try block, then you'll need an additional interface that is essentially a factory for a stream. So the first argument to withClose would be of type StreamFactory<T>, not T. That would be an additional complication. Overall, I do like the idea, but I think I would prefer the ARM proposal to become a reality.

Neal Gafter said...

http://www.javac.info