|
Closures
How lambdaj 2.0 brings (almost) real closures to Java
lambdaj's closureClosures represent probably the most important feature that is missing in the tools box of each Java programmer. Some of them don't feel (or don't understand) the need of closures, while some other does and probably for this reason are evaluating to migrate toward a functional enabled programming language. The biggest part of us just learned to partially workaround this lack by using the verbose and poorly readable (anonymous) inner classes. lambdaj tries to partially fill this lack by introducing in its release 2.0 a new feature that allow to define, in its traditional DSL style, first-class functions with free variables like in the following example: Closure println = closure(); { of(System.out).println(var(String.class)); }I believe it is straightforward to understand what the println closure does. In particular the var() method binds a free variable of type String to the closure. Moreover, note that the curly brackets around the statement that define the behaviour of the closure are only syntactic sugar and then could be safely removed even if I find the code more readable by keeping them. Also note that the method invoked on the object returned by the of(...) statement cannot be a private one. You can then invoke this closure by "closing" its free variable once: println.apply("one");or more times: println.each("one", "two", "three");As you can expect this last statement will cause the Strings "one", "two" and "three" to be printed on 3 different lines of the Java standard output. It is possible to create both untyped (as in the former example) and strongly typed closure. Supposing your classes has a method that sums 2 ints: public int sum(int a, int b) {
return a + b;
}it is possible to instance a closure that invokes this method as it follows: Closure2<Integer, Integer> adder = closure(Integer.class, Integer.class); { of(this).sum(var(Integer.class), var(Integer.class)); }While you were allowed to call the first closure with any type and number of variables (it will eventually throw an Exception if invoked in a wrong way), you can invoke this second one only by passing 2 ints to it as expected: int result = (Integer)adder.apply(2, 3);
CurryAnother feature typically available on closures is the so called curry that allows to create another closure by fixing the value of some free variables. For example you can have a closure of one free argument that adds 10 to any number by doing a curry of the second variable of the former closure as it follows: Closure1<Integer> adderOf10 = adder.curry2(10); In this way by invoking this last closure with the value 3: int result = (Integer)adderOf10.apply(3); you will obtain 13 as expected. In the end, note that you could achieve exactly the same result by directly creating a closure with only one free parameter and the second one already fixed to 10 as in this last statement: Closure1<Integer> adderOf10 = closure(Integer.class, Integer.class); { of(this).sum(var(Integer.class), 10); }Casting a closure to a one-method interfaceSometimes, especially when you have to use some existing API, you could still need to have a closure declared in the standard Java fashion: an interface that declares just a single method. In this case it is possible to cast a closure in that interface, leaving to lambdaj the burden to check if they are compatible. For example, if you need a lambdaj Converter that changes an Integer in another Integer 10 units bigger than the first one, you can cast the former closure as it follows: Converter<Integer, Integer> converterAdding10 = adderOf10.cast(Converter.class); Why are closures useful?If you still don't see how closures can be useful for you, let me make a slightly more complex example that could show why I think that they are the most powerful tool in order to generalize your code and thus avoid duplications. At this purpose let's write a method that reads a file from the classpath and then prints its content line by line on the Java standard output: public void printFile(String fileName) {
BufferedReader reader = null;
try {
InputStream stream = getClass().getClassLoader().getResourceAsStream(fileName);
reader = new BufferedReader(new InputStreamReader(stream));
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
System.out.println(line); // This is actually the only meaningful statement in this method
}
} catch (IOException ioe) {
throw new RuntimeException("Error while reading file " + fileName, ioe);
} finally {
try {
if (reader != null) reader.close();
} catch (IOException ioe) {
throw new RuntimeException("Error while closing file reader", ioe);
}
}
}There are lots of bloatware and maybe just one meaningful line of code in this method, isn't it? But now suppose you also need a method that write the file into a String and another one that just counts the number of non-empty lines in the file itself. Instead of copy and paste the former method other two times and change just a single statement in each new method, I think that could be a better idea to generalize it, by passing a closure that tells to the method, case by case, how a line read from the file should be managed, as it follows: public void printFile(String fileName) {
Closure1<String> lineReader = closure(String.class); { of(System.out).println(var(String.class)); }
readFileByLine(fileName, lineReader);
}
public String readFile(String fileName) {
StringWriter sw = new StringWriter();
Closure1<String> lineReader = closure(String.class); { of(sw).write(var(String.class)); }
readFileByLine(fileName, lineReader);
return sw.toString();
}
public int countFileLines(String fileName) {
lineCounter = 0;
Closure1<String> lineReader = closure(String.class); { of(this).countNonEmptyLine(var(String.class)); }
readFileByLine(fileName, lineReader);
return lineCounter;
}
private int lineCounter = 0;
void countNonEmptyLine(String line) {
if (line != null && line.trim().length() > 0) lineCounter++;
}
private void readFileByLine(String fileName, Closure1<String> lineReader) {
BufferedReader reader = null;
try {
InputStream stream = getClass().getClassLoader().getResourceAsStream(fileName);
reader = new BufferedReader(new InputStreamReader(stream));
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
lineReader.apply(line);
}
} catch (IOException ioe) {
throw new RuntimeException("Error while reading file " + fileName, ioe);
} finally {
try {
if (reader != null) reader.close();
} catch (IOException ioe) {
throw new RuntimeException("Error while closing file reader", ioe);
}
}
}Closure's delayed evaluationLambdaj also provides a different usage pattern called delayed evaluation because the invocation of the closure itself it is delayed until it gets defined. That in turns allows to use closures in a more syntetic and elegant way as in the last example below. Note that here the closure creation has been moved inside the method that uses it, while the corresponding definition comes immediately after the invocation of that method itself. public void printFile(String fileName) {
readFileByLine(fileName); { of(System.out).println(var(String.class)); }
}
public String readFile(String fileName) {
StringWriter sw = new StringWriter();
readFileByLine(fileName); { of(sw).write(var(String.class)); }
return sw.toString();
}
public int countFileLines(String fileName) {
lineCounter = 0;
readFileByLine(fileName); { of(this).countNonEmptyLine(var(String.class)); }
return lineCounter;
}
private int lineCounter = 0;
void countNonEmptyLine(String line) {
if (line != null && line.trim().length() > 0) lineCounter++;
}
private void readFileByLine(String fileName) {
delayedClosure(new DelayedClosure<Void>() {
@Override
public Void doWithClosure(Closure lineReader) {
BufferedReader reader = null;
try {
InputStream stream = getClass().getClassLoader().getResourceAsStream(fileName);
reader = new BufferedReader(new InputStreamReader(stream));
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
lineReader.apply(line);
}
} catch (IOException ioe) {
throw new RuntimeException("Error while reading file " + fileName, ioe);
} finally {
try {
if (reader != null) reader.close();
} catch (IOException ioe) {
throw new RuntimeException("Error while closing file reader", ioe);
}
}
return null;
}
});
}This syntax, while already available in the 2.0 release has been vastly improved in the 2.1 one. Moreover it also now allows to return a value from the method that uses the delayed closure as shown in the examples provided in the lambdaj 2.1 release notes. |
Sign in to add a comment
But these "closures" are just plain lambdas with an arbitrary number of params (actually up to four, if I interpret the source correctly). The point of a closure is to close over the variables in the lexical scope. I am not complaining about the lack of functionality, but about the usage of the term "closure" in this text.
The variables in the lexical scope to be closed over are the ones contained in the object passed to the of() method. A quick example could clarify this point. Suppose you have the following class:
public class AdderOf { private int first; public AdderOf(int first) { this.first = first; } public add(int second) { return first + second; } public int getFirst() { return first; } public void setFirst(int first) { this.first = first; } }and then you define a closure as this one:
AdderOf adderOf = new AdderOf(10); Closure<Integer> closure = closure(Integer.class) { of(adderOf).add(var(integer.class)); }Now if you invoke this closure by passing 5 to it:
you will obtain 15 as expected. But after changing the property of the closed object
and repeating the same closure invocation you will now obtain 20.
Please let me know if this is clear and if you think it matches the closure definition.
This is clear. I don't know if it's useful, but it certainly doesn't match any definition of "closure" that I'm familiar with.
Accordingly to wikipedia: "a closure is a first-class function with free variables that are bound in the lexical environment". Could you please explain why the variable first of the AdderOf class in my example cannot be considered such as the free variable in the former definition?
I think people are been harsh saying that these aren't closures, OK the syntax is different but the effect is the same. They are Semantically, possibly excluding primitives, the same as closures. There seems to be a bit of an agenda saying that anything that isn't exactly like my proposal is in some way lacking. Great work, shows what can be done without major changes.
One of these wiki pages could actually include the line: import static ch.lambdaj.Lambda.;
Sorry. I gave it for granted, but since it doesn't seem to be so clear I will update the documentation. Thank you for your advice.
I think the only problem in the text above is that you use the term "to close over" a variable where what you actually do is apply your lambda to a specified argument. The end-user point of having a true closure that closes over variables in the lexical scope is being able to write code that seamlessly integrates what are technically function definitions into the general flow of a method's code, so that we have an effect of lifting a piece of our code and saving it for later execution, or, more typically, hand it over to another function to call back. So, for example, we can write
int a = (some complex logic calculating a); List<Integer> numbers = ...; List<Integer> convertedList = convert(numbers, { int b = someFunction(a); int c = (result of more calculations involving a and b); return c; });I actually think your take on closures might cover that use case (except that the implementation of a closure can come from only a very narrow set of Java statements). In your "on" expressions, if a variable from the surrounding scope appears, its value will be used while forming the lambda.