Like Generics, one equally important feature that the Java language has been wanting is Closures. A JSR for closures resulted in multiple proposals. Notably,
As a note a poll taken to determine which proposal Java developers would welcome led to, BGGA taking the lead. Interestingly enough, a large percentage of Java developers felt that they do not want Closures in Java. My intuition tells me that a large percentage of the latter voters are saying "I do not want BGGA closures in Java" rather than "I do not want Closures at all".
A famous debate between Neal Gafter who has come forth as the major spokesperson for the BGGA closures proposal and Josh Bloch, a supporter of the CICE proposal has ensued via presentations and blogs. The same can be googled for.
A bit about my stance, I definitely see the power of Closures. However as the saying from Spiderman, the movie goes, "With great power comes great responsibility." I want the power that Closures offer in Java, and I want an implementation that I can understand and be productive with. I want intuitive compilation errors that will immediately alert me to the problem without straining the few @deprecated Grey cells that are still struggling to function in my brain.
I want conciseness, but not at the cost of readability and maintainability. In short, I want power with clarity and brevity. One cannot always get everything they want, sad but true, however, my stance is that if I can benefit considerably most of the time, I do not mind the pain for a little of the time. Think of it like a marriage ;-)))..luckily, the wife does not read my technical blogs lol! A better parallel; (Gain Factor >>>> Pain Factor == Adoption)
One can read about what a Closure is, what a Lambda expression is and I will not spend time on the same. Primary reason being, I cannot understand the same myself :-(
What I want to do in this Blog is to understand Closures by BGGA and try for myself how hard or how easy is it to work with them. I do not use an IDE but revert to using emacs and the command line for this exercise.
I downloaded the BGGA propopal and ran some of the examples from the Java Closures Tutorial so very well explained by Zdeněk Troníček. Then I tried to apply the same to cases I have encountered before or am working with now to see for myself how easy/difficult the proposal is and problems I faced, if any.
Function Types:
I think function types would be a great addition to the language. I understand Mr.Bloch's concern that they might tend to lead to an exotic style of programming, however, I feel exotic styles of programming can even be developed with the simplest of constructs should developers choose to. It is possible for a psychotic developer as myself to take the simplest of problems and provide the most convoluted but workable solutions for the same. If I were introduced to the book Java Puzzlers prior to learning Java, I might have fled the scene. Sure, there will be puzzling cases but thats what code reviews, documentation, etc are all for after all. I am not sure I buy the argument, that "BGGA closures lend themselves to an exotic programming style". One can easily be exotic with the simplest of language constructs IMHO.
Lets look at a couple of examples, I have an eachEntry() method that runs through an java.lang.Iterable and executes the block provided. I also a have a simple filter() that can be used to filter an Iterable which delegates to the eachEntry().
public static <T> Collection<T> filter(Iterable<T> input,
{T=>boolean} filterBlock) {
Collection<T> filteredItems = new ArrayList<T>();
eachEntry(input, {T item =>
if (filterBlock.invoke(item)) filteredItems.add(item);});
return matched;
}
public static for <T, throws E extends Exception>
void eachEntry(Iterable<T> items, {T => void throws E} block) throws E {
for (T item : items) {
block.invoke(item);
}
}
An example usage of the above where Employee objects are filtered:
// Static import the utility functions, filter and eachEntry
import static com.welflex.util.Utils.*;
List<Employee> emps = ...// Get employees
// A closure that uses the Function type of eachEntry to dump out the
// information about each employee
eachEntry(emps, {Employee e =>
System.out.println("Id:" + e.getId()
+ ", Manager:"
+ e.isManager()
+ ", Salary:" + e.getSalary());});
// Filter out only employees who are managers
Collection<Employee> managers = filter(emps, {Employee e => e.isManager()});
// Filter out only managers who earn gt 60000 using a filter of filters
Collection<Employee> mgrsGt = filter(filter(emps, {Employee e => e.isManager()}), {Employee e => e.getSalary() > 60000);
// The above one re-written
mgrsGt = filter(emps, {Employee e = > e.isManager() && e.getSalary() > 60000});
I find the above pretty concise and powerful. I also find the same rather easy to read as well. So no complaints from me there. For those reading, hey I never claimed I was invincible as far as programming goes, close to invincible, yes ;-)
Now the negative, some things I could however not understand,
a. for construct did not work for me:
"for" construct did not work for me. I might not have done it correctly, but the eachEntry() that uses the "for" construct compiled just fine. Compiling the following should have worked, I am not sure what I am missing:
for eachEntry(Employee e : emps) {
System.out.println("Id:" + e.getId());
}
A compilation error leading to:
"Filter.java:20: method forEach in class Filter cannot be applied to given types
required: java.lang.Iterable
found: java.util.Collection
forEach(orig, {T item => if (block.invoke(item)) matched.add(item);},"
The above was not very instructive for me in addressing the issue :-(
b. Static Inner Class when used in the example caused compiler error:
When my Employee class was a static inner class, I ended up with the following exception when I tried to compile the examples class, leading me take it the Employee class to its own separate compilation unit:
exception has occurred in the compiler (1.6.0_11). Please file a bug at the Java Developer Connection (http://java.sun.com/webapps/bugreport) after checking the Bug Parade for duplicates. Include your program and the following diagnostic in your report. Thank you. java.lang.NullPointerException at com.sun.tools.javac.comp.Resolve.isAccessible(Resolve.java:140)
I would like to also provide another example where I am using an ARM block to obtain a JMS Object from JNDI and subsequently closing the context. Note that I am not using actual javax.naming.Context classes but have instead modeled my own for the sake of demonstration. Without closures and using anonymous classes, I have the following:
private <T> T executeContextTask(ContextTask<T> task) throws LookupException {
Context ctx = null;
try {
ctx = new ContextImpl();
return task.executeWithContext(ctx);
} finally {
if (ctx != null) {
try {
ctx.close();
} catch (Exception e) {
//log this
}
}
}
}
private static interface ContextTask<T> {
public T executeWithContext(Context ctx) throws LookupException;
}
public ConnectionFactory getConnectionFactory(final String name)
throws LookupException {
return executeContextTask(new ContextTask<ConnectionFactory>() {
public ConnectionFactory executeWithContext(Context ctx)
throws LookupException {
ConnectionFactory c = (ConnectionFactory) ctx.lookup(name);
// Do something with Connection Factory before returning
return c;
}
});
}
....
public Destination getDestination(final String name)
throws LookupException {
... // Similar to above..except returns a Destination..//
}
The same above example when done using BGGA closures is simplified to:
private static <T, throws E extends LookupException>
T withContext({Context=>T throws E} contextTaskBlock) throws E {
Context ctx = null;
try {
ctx = new ContextImpl();
return contextTaskBlock.invoke(ctx);
} finally {
if (ctx != null) {
try {
ctx.close();
} catch (Exception e) {
// Log error
}
}
}
// Note name is no longer final, neither is there a class called
// Context Task and the anonymous class has been replaced by the
// closure.
public ConnectionFactory getConnectionFactory(String name)
throws LookupException {
return withContext({Context ctx =>
ConnectionFactory c = (ConnectionFactory) ctx.lookup(name); c});
}
.. Similar code for Destination...
Closure Conversion:
I quite like Closure Conversion. For example, I have an interface called Read that reads in an Employee Record. Assigning a closure to the interface type we have the below:
interface Read {
public Employee read(String criteria);
}
Read readRecord = {String criteria =>
System.out.println("Criteria:" + criteria);
Random r = new Random();
new Employee(r.nextInt(5),
r.nextBoolean(),
r.nextInt(10000))};
public void closureConversion() {
Employee e = readRecord.read("select * from Emp where id=\"20\"");
System.out.println("Employee Read:" + e);
}
Arrays:
Arrays have boggle my mind in Generics, Arrays continue to boggle my mind with BGGA closures.
// Will not compile
{ => int} closures[] = new { => int}[10];
// Will not compile
{=>int} closures[] = {{=>5}, {=>6}};
// Compiles
List<{=> int}> list = new ArrayList<{=>int}>();
// But no way of
{=>int} closures[] = list.toArray({=>int} closures[0]);
Non local returns:
When returning from a Closure, should the "return" result in returning from the method itself? That is not very intuitive to me. I am of the opinion that returns from a closure, should only be local and not result in exiting the method. That is room for accidental programing with disastrous results.
Consider the following Code, what do you thing will be the result of the invocation?
static boolean testBooleanLocal(boolean input) {
{boolean =>boolean} closure = {boolean args=>
args
};
boolean result = !closure.invoke(input);
System.out.println("Test Boolean Local Result: " + result);
return result;
}
static boolean testBooleanNonLocal(boolean input) {
{boolean ==> boolean} closure = { boolean arg ==>
return arg;
};
boolean result = !closure.invoke(input);
System.out.println("Test Boolean NonLocal Result:" + result);
return result;
}
public static void main(String args[]) {
System.out.println("Local:" + testBooleanLocal(true));
System.out.println("Non Local:" + testBooleanNonLocal(true));
}
The answer is:
Test Boolean Local Result: false
Local:false
Non Local:true
What just happened to the second println()? All because I had a ==> versus a => along with a return statement?
The Syntax:
Without the assistance of an IDE, I had a few growing pains where I was accidentally doing "= >" instead of "=>", i.e, a space in between. Surely any IDE ready to support BGGA will catch the same. What about "==> "versus "=>"? Will an IDE be able to warn regarding the difference? I sure hope so :-)
Running the Examples:
As always, I am sharing my playground. For the ones interested, I tried the same using:
Download the ...., install the same and then set your PATH to include the existing JDK 1.6. For example,
export PATH=/.../jdk1.6.12/bin:$PATH. Then compile the provided code using the "javac" from the BGGA proposal, for example, "$BGGA_HOME/bin/javac -classpath .:com/welflex/utils:com/welflex/model:com/welflex/example *.java" and run the same using the similar syntax.
Parting Thoughts:
Sadness that Java does not have closures. Visual Basic developers are benefitting and thriving with the same, something I heard. Closures are a construct that has been around since ages, Java compromised at the time of inception via Anonymous classes rather then deal with the complexity of Closures, maybe a good decision for the time. However, time is now neigh for change. If Java has to survive, Closures need to enter the language. One cannot wait between JDK7 to JDK8 without knowing closures will or will not find a place in Java. Java folk are tending to gravitate toward other languages like Scala, Clojure etc. IMHO we need to stop this abandonement. My rationale for the same is (based purely on sentiments), I love Java, its been the reason for my bread and butter over the years, I love developing with it and I want to continue to develop with it. More importantly, its represents Coffee, I cannot wake up without my shot of the same. Scala or Clojure doesn't inspire me as much, maybe an alternate VM language called Nicotine could cut it and spark my interest ;-)
BGGA is not perfect. Sadly, nothing ever is. In my own humble opinion, the warring factions of Gafter/Bloch need to join forces to improve the compiler warnings, syntax and any other ambiguities in the features of BGGA present and look toward introducing it as part of JDK 8.X. Come on guys, you are super smart chaps (Phd's don't come easy) that have collectively led to the creation/enhancement of this great language, make my life easy and do not let me leave my favorite language for some pretenders to the throne :-(. If not, I propose that Gosling take the place of a dictator and get this in..I will welcome the same! Maybe IBM buying Sun will change things... Got to cut em beers when I post, for all you know I might have made a case for "No closures ever in Java via my misadventures :-)", lol!
Sample Code that I have used can be downloaded from here.
Resources: