… but this ain’t one.
Today Yahuda Katz posted his 10 favourite things about the ruby language. His list certainly reflects most of the things I find very appealing about the language. The sixth item highlights Ruby’s excellent support for blocks and lambdas and an argument is mounted that when performing file operations in languages without them, programmers are forced to use an inline “ensure” block every in the same lexical scope that they originally opened the file in, to ensure that the resource is closed.
As is often the case the comparison is made to Java, but as any seasoned Java developer will tell you (and this point has probably be made many, many times) it just isn’t true. Java supports a safer and comparable idiom via the anonymous inner class.
The example given is a very succinct Ruby method to print out the lines of a text file. Ruby’s File.open ensures the file is properly closed after the block has completed, normally or otherwise:
def run(input)
File.open(input, "r") do |f|
f.each_line {|line| puts line }
end
end
Code language: Ruby (ruby)
The Java version listed does the same but, predictably, is far more verbose and laborious:
static void run(String in)
throws FileNotFoundException {
File input = new File(in);
String line; Scanner reader = null;
try {
reader = new Scanner(input);
while(reader.hasNextLine()) {
System.out.println(reader.nextLine());
}
} finally { reader.close(); }
}
Code language: JavaScript (javascript)
However no Java developer that values their sanity is ever going to sprinkle this pattern through their code base any time they want to use a File and ensure that it is closed properly. Here’s what they are going to use instead:
public interface Processor {
void process(T target);
}
Code language: PHP (php)
import java.io.*;
import java.util.Scanner;
public class FileSlurper {
public static void slurp(String fileName, Processor processor)
throws FileNotFoundException {
File input = new File(fileName);
Scanner reader = null;
try {
reader = new Scanner(input);
processor.process(reader);
}
finally {
reader.close();
}
}
}
Code language: JavaScript (javascript)
Once that infrastructure is in place, the Java implementation of the example becomes:
FileSlurper.slurp(input, new Processor() {
public void process(Scanner reader) {
while(reader.hasNextLine())
System.out.println(reader.nextLine());
}
});
Code language: JavaScript (javascript)
No one can argue that this is as pretty or convenient as the Ruby version, but it does ensure that file opening and closing is handled correctly without any effort from the client code. The code in the anonymous inner class can even access variables in the current lexical context, as with the Ruby block, with the caveat that they must be declared final (not quite a lexical closure!). This pattern, in conjunction with the Processor interface may be used any time there is a need for similar resource acquisition and clean–up procedures, as might be the case for locks or database connections.
Lambdas and blocks are very useful constructs, and the anonymous inner class can act as an acceptable, if far from ideal, analogue. What an anonymous inner class can’t do is act as a co–routine; this is a truly powerful feature of Ruby’s blocks and associated method invocation features that Java can’t directly compete with.
One thing’s for certain: the Ruby community won’t win over Java developers by giving examples of deficiencies where none exist.
Leave a Reply