Summary
There are at least 4 inner class/closure proposals for Java 7 and it is hard to compare them because they use different terms and different syntax for similar concepts. This post makes the comparison easier by seperating concerns.
Advertisement
Comparing Inner Class/Closure Proposals
There are at least 4 inner class/closure proposals for
Java 7 and it is hard to compare them because they use different terms and
different syntax for similar concepts.
The idea of this post is to separate out the inner class/closure part of
the proposals from the extras and make explicit that the extras can
be added or taken away from any of the proposals or be part of other proposals;
because they are separate concerns.
This separation of concerns gives clarity and also allows you to roughly
rank the various features (my ranking is the order presented).
In some instances
there is synergy between these separate concerns and hence these extras are
presented together with the main proposal. In fact all but the
CICE
proposal covers issues other
than the inner classes/closures themselves. Before continuing I should declare
my self-interest; I authored the
C3S proposal.
Explanations and examples of the
different features are given below, but first an “at a glance” comparison.
1. Short syntax for the creation of an instance of an inner class/closure
Y
Y
Y
Y
2. Access to both this pointers and methods within an inner class
Y
Y
3. More than one method in an inner class/closure instance
Y
4. Implementation of methods defined in classes
Y
Y
5. Type inference
Y
Y
Y
Y
6. Method, constructor, and field literals
Y
7. Short syntax (particularly for control structures and short methods)
Y
Y
Y
8. Assignment to local variables and no final requirement
Y
Y
Y
Y
9. Variable number of exceptions
Y
Y
Y
Y
10. Method/function types (with shorter syntax)
Y
Y
11. Non-local, return, break, and continue
Y
Y
Y
Definition of terms
Closure is a new term in the current Java 6 context; closures are like inner
classes except that the normal inner class this pointer isn't available,
only the pointer to the enclosing class, which is confusingly (?), called
this.
C3S
is Clear, Consistent, and Concise Syntax for Java
FCM
is First Class Methods v0.5 and the companion Java Control Abstraction (JCA)
BGGA
is Closures for the Java Programming Language v0.5 (named after the initials
of the authors)
Summary of features
From the table you can see that the proposals only really agree that there
should be: short syntax for inner classes/closures, more type inference,
a variable number of exceptions,
and that the read-only and final declaration restriction for
local variables should be removed. Should these be the only extensions made
to Java?
As for the other features, that depends upon the weighting
you give each feature. This weighting need not be a simple progression
of a weighting of 11 for your number 1 to a weighting of 1 for your number
11. For example I personally give little weighting to my numbers 8 to 11
inclusive; however others find these points important and therefore I have
included some of them in my C3S proposal to hopefully broaden acceptance.
Explanations and examples
1. Short syntax for creation of an instance of an inner class/closure
This is the focus of all the proposals and a typical use
is to apply a method to the elements in a collection.
2. Access to both this pointers and to methods within an inner class
This is the real distinguishing feature between a closure
and an inner class, a closure only has access to the this
pointer associated with the enclosing
class and not, also, its inherited this pointer.
Therefore inside a closure you cannot call any other method of the closure including
itself (inner classes don’t have these restrictions). C3S and CICE have inner
classes, whereas FCM and BGGA only have closures. Anything that can be done
with a closure can therefore be done with an inner class but not vice
versa. For inner classes there may be a case for adding a keyword, say
enclosing, that is used like this,
but refers to the enclosing class
(what do people think about adding this keyword?). For me this is a very
important feature, so I will give three examples.
A. Reducer
Firstly, consider a
reduce method (a.k.a. fold) on a list. If you had a class
Reducer as shown, using Java 6 syntax:
public abstract class Reducer< E > {
public E total;
public Reducer( final E initial ) { total = initial; }
public abstract void call( E element );
public static < T > T reduce( final Iterable< T > collection, final Reducer< T > reducer ) {
for ( final T element : collection ) { reducer.call( element ); }
return reducer.total;
}
}
then
C3S
method Integer sum() {
return reduce ls, new( 0 ) { method( e ) { total += e } };
}
FCM (Only one this, therefore make total explicit
and use each instead of reduce.)
Integer sum() {
Integer total = 0;
each( ls, #( Integer e ) { total += e; } );
return total;
}
Alternatively reducer could be re-written to pass the
total each
time and then you could write:
CICE (Constructor must have zero arguments, therefore
use straight Java 6 code. Alternatively code similar to FCM above could
be used.)
Integer sum() {
return reduce( ls, new Reducer( 0 ) {
public void call( e ) { total += e; }
} );
}
BGGA (Only one this, therefore make total explicit
and use each instead of reduce.
Code not shown since it is similar to FCM above.)
B. Recursion
The second example of needing access to the methods inside
the inner class is recursion (without access to methods within the inner
class you cannot use recursion — it is a long while since I used a language
without recursion :-( ). Given a list of Integers:
C3S
method Integer sumOfFactorials( final Iterable< Integer > ls ) {
return reduce ls, new( 0 ) {
method( e ) { total += factorial e }
static method int factorial( final int x ) {
if ( x <= 1 ) { return 1 }
x * factorial x – 1
}
};
FCM (No access to methods within the closure from within
the closure, therefore use normal Java 6.)
Integer sumOfFactorials( final Iterable< Integer > ls ) {
return reduce( ls, new Reducer< Integer >( 0 ) {
public void call( e ) { total += factorial( e ); }
static int factorial( final int x ) {
if ( x <= 1 ) { return 1; }
return x * factorial( x – 1 );
}
};
}
CICE (Constructor must have zero arguments and only
one method allowed, therefore use straight Java 6 code as above.)
BGGA (No access to methods within the closure from
within the closure, therefore use Java 6 as shown for FCM above.)
C. Worker
With the advent of multi-core processors the importance of multi-threaded
code will increase. Any new language features should anticipate this trend
and a favour multi-threading. Access to the enclosing scope is of less value
in a multi-threaded environment since the enclosing scope may be long gone
when the method is evaluated. Multi-threading therefore emphasizes access
to the inherited this pointer and to methods and fields within
an inner class.
For example, at times you need to wait for previous tasks to complete before
proceeding. This can be done by waiting for Futures to complete;
a Future's get method will sleep a thread for example.
Unfortunately a Thread is expensive and therefore you ideally
don't want to sleep one. Instead you might delay scheduling a calculation until
its inputs are ready:
static method< R, A1, A2 > Future< R > submitWithGuard(
final ExecutorService pool,
final Future< A1 > a1,
final Future< A2 > a2,
final Method2< R, A1, A2 > binary ) {
final result = FutureTask< R >.new method { binary.call a1.get, a2.get };
pool.submit method {
if ( a1.isDone && a2.isDone ) { pool.submit result } // inputs available, submit job
else { pool.submit this } // inputs not available, check again in the future
};
return result;
}
FCM and BGGA cannot implement submitWithGuard, as
shown above, because it accesses the inherited this and instead normal Java
6 would be used or a name would be given to the inner class via a method declaration
(which would then be wrapped in a Runnable). A CICE implementation
would be similar to the above code, but a little more verbose.
3. More than one method in an inner class/closure
C3S is unique in allowing the overriding of more than one
method. The others can only override one method and therefore some uses,
particularly for asynchronous calls, can’t be accomplished directly. EG imagine
a method called time that
takes an array of Callable methods,
runs them all a few times and in different orders, and then reports the average
execution time for each as well as checking each call gives the same result.
This time method requires
objects that have a call method
from Callable, but also uses
the toString method for identification
and reporting purposes.
FCM (Only one method allowed therefore use straight Java 6.)
time( new Callable< Integer >() {
public Integer call() { ...; return ...; }
public String toString() { return "Method1"; }
}, new Callable< Integer >() {
public Integer call() { ...; return ...; }
public String toString() { return "Method2"; }
} );
CICE (Only one method allowed therefore use straight Java 6, as shown above for FCM.)
BGGA (Only one method allowed therefore use straight Java 6, as shown above for FCM.)
4. Implementation of methods from classes
No problem with C3S and no problem for CICE provided that
for CICE there is only one abstract method to override (see point 3 above).
FCM can implement a method from a class provided that only one method is
to be implemented, the class doesn't have generic arguments, the class
has a no-arg constructor, and no access is required to other class members.
BGGA can’t use classes
at all. Consider an Integer array
factory (a factory that returns a List of Integers,
but the list is fixed sized):
C3S
static method List< Integer > factory( final int capacity ) { // final optional
return AbstractList.new< Integer > {
private final values = Integer.new[ size ];
method size { capacity };
method get( index ) { values[ index ] }
method set( index, value ) {
final temp = values[ index ];
values[ index ] = value;
temp
}
}
}
FCM (No access to inherited this (see point 2 above), therefore
use standard Java 6.)
static List< Integer > factory( final int capacity) {
return new AbstractList< Integer >() {
private final Integer[] values = new Integer[ size ];
public int size() { return capacity; }
public Integer get( final int index ) { return values[ index ]; }
public Integer set( final int index, final Integer value ) {
final Integer temp = values[ index ];
values[ index ] = value;
return temp;
}
};
}
CICE (Cannot override more than one method (point 3
above), therefore use standard Java 6 (see FCM above).)
BGGA (Cannot extend abstract classes at all,
therefore use standard Java 6 (see FCM above).)
5. Type inference
C3S, FCM, and BGGA have some type inference and CICE states
that type inference could be added. The type inference for C3S
(but see below for other examples),
FCM, BGGA, and suggested for CICE is to infer a class/interface/method name.
EG the sort example given in 1 above which in C3S is:
Infers: the interface name, Comparable,
the generic type parameter, String,
the return type, int, the
method argument types, both String,
and the method name, compare.
The examples for BGGA and FCM are almost identical, except that the argument
type is needed, and therefore FCM and BGGA are not shown; for CICE you need
to supply the interface name and argument types (see 1 above). The type inference
in C3S is however much more extensive than for the other proposals and more
extensive than the above example demonstrates. The C3S type inference is
similar in scope and nature to the type inference that is in Scala. This
copying of Scala is an important point, since Scala demonstrates that this
level of type inference is practical and still produces good error messages
unlike more extensive type inference in other languages. In C3S the method
construct given above can be used for any declaration,
e.g. an anonymous AbstractList:
static method List< Integer > factory( final int capacity ) {
return AbstractList< Integer >.new {
private final values = Integer.new[ size ];
method size { capacity };
method get( index ) { values[ index ] }
method set( index, value ) {
final temp = values[ index ];
values[ index ] = value;
temp
}
};
}
Also in the AbstractList example above note how
the type of variable declarations are inferred from the right hand side.
To support this type inference a new keyword declare is
added for non final declarations, e.g.:
declare list = ArrayList.new( 1, 2, 3 );
Instead of:
List< Integer > list = new ArrayList< Integer >( 1, 2, 3 );
6. Method, constructor, and field literals
The FCM proposal provides syntax for method, constructor,
and field literals (currently only a type has a literal in Java,
name.class, and strings have to be used
for the others). The proposal in FCM mimics the construct used in Javadocs,
e.g.:
Method m = ClassName#methodName( argumentTypes );
Constructor< ClassName > c = ClassName#ClassName( argumentTypes );
Field f = ClassName#fieldName;
A unique and emphasized feature of FCM is the ability to refer to methods
as an alternative to writing an inner class/closure. E.G.:
public void init() {
JButton button = ...;
button.addActionListener( this#handleAction( ActionEvent ) );
}
public void handleAction( ActionEvent ev ) {
// handle event
}
This syntax allows you to seperate out the method to a stand alone method.
Assuming that no access to local variables is required. The above code could
alternatively be written as:
button.addActionListener( #( ActionEvent e ) { handleAction( e ); } );
7. Short syntax (particularly for control structures and short methods)
C3S, FCM, and BGGA provide short syntax for constructs
other than inner classes/closures, the other proposals don’t address further
short syntax. First BGGA, it has two other short syntax constructs
one for short methods and one for control like constructs. The short method
construct is from within a closure only (i.e. not generally available).
The short method construct is that return isn’t used; instead
a statement without a terminating semicolon must be used instead.
E.G.:
(Note { => true; }, { => return true } and
{ => return true; } might not behave how you expect.)
BGGA also provides short syntax for control structures,
it uses the for keyword to
identify these methods and allows the movement of the closure to outside
the method brackets, provided that the closure is the last argument. E.G.
an each iterator might be:
public static < V > void for each( Iterable< V > collection, { V => void } block ) {
for ( V v : collection ) { block( v ); }
}
...
for each( Integer e : ls ) { total += e; }
If each where not declared with for
then its use in BGGA it would be:
public static < V > void each( Iterable< V > collection, { V => void } block ) {
for ( V v : collection ) { block( v ); }
}
...
each( ls, { Integer e => total += e; } );
FCM does not have any short method syntax but it does have control syntax,
much along the lines of BGGA and therefore not shown.
C3S takes a different
approach than BGGA, it provides general short constructs, not just specifically
for inner classes, that can be used throughout Java and all of which are
optional. Type inference, discussed above, is an example. Others examples
are: method declarations in general, generic declarations, inference of generic
types for constructors, declaration of constructors, and declaration of properties.
See C3S for
details. It is interesting to note that in the vast majority of examples
C3S versions are the most concise; this is a surprising result since C3S’s
design favoured clarity and therefore used keywords rather than symbols and
since the keywords are longer than the symbols used in other proposals you
might expect C3S to be verbose. The reason that C3S is the most concise, is
because generally applicable constructs have been favoured in C3S over constructs
with a few use cases.
For all methods C3S makes return optional
and uses the value of the last line in the block if return is
omitted, therefore the invert example
above in C3S would be:
(Note { true; }, { return true } and { return
true; } all do the same as { true }.)
For all methods C3S makes unambiguous brackets optional
(like Ruby) and makes the semicolon before a closing brace optional (like
Pascal), therefore the for example in C3S is:
each ls, method( e ) { total += e };
8. Assignment to local variables and no final requirement
All the proposals are basically the same, they allow writing
to local variable (in the case of CICE a writable local needs to be annotated
with public) and there is no need to declare local variables final.
Since all the proposals are so similar on this point only C3S is shown:
method int sum() {
int total = 0;
each ls, method( e ) { total += e };
return total;
}
In BGGA you can turn off access to non-final local variables by making
the interface that the closure implements the method of extend
RestrictedFunction. It is not clear how practical this
technique of adding RestrictedFunction would be since
you can't retrofit to existing code easily.
9. Variable number of exceptions
At times it is convenient to be able to declare very general
methods that throw any number of exceptions, including checked exceptions.
Currently either RuntimExceptions only are
allowed, e.g. Runnable, or Exception,
as opposed to a specific exception list, is thrown, e.g. Callable.
C3S, FCM, and BGGA propose solutions, C3S uses generic varargs and FCM and
BGGA provide a similar capability but don’t use the varargs syntax.
C3S
interface< R, A1, Throwable... Es > Method1 {
method R call( A1 a1 ) throws Es;
}
FCM & BGGA
interface Method1< R, A1, throws Es > {
R call( A1 a1 ) throws Es;
}
CICE
Joshua Bloch (one of the authors of CICE) said in an email:
While CICE [JB said BGGA but I am sure he meant CICE] doesn't specifically allow for disjunctively typed throws clauses (“Variable number of exceptions”) it doesn't rule it out either. I see this as a defect in the generic typing facility that should be fixed there. Once so fixed, it will apply to all parameterized types, even those produce using old-fashioned anonymous class instance creation expressions.
This is a fair point, “Variable number of exceptions” is a seperate concern.
10. Method/function types (with shorter syntax)
FCM and BGGA both provide support for declaring the type
of classes that contain a single method. In both cases the syntax is based
on their inner class/closure syntax, e.g.:
#( void( MouseEvent ) ) me = #( MouseEvent e ) { ...; ...; }; // FCM
{ MouseEvent => void } me = { MouseEvent e => ...; ...; }; // BGGA
In both cases the types above are translated into a standard interface, e.g.:
Method1< Void, MouseEvent >
See point 9 above for the definition of Method1. To
convert the anonymous method given in the above example to an instance of
Method1 the correct generic parameters for Method1 need to be inferred. The
generic arguments are easy, since they are explicitly stated, the return
type of the anonymous method is however potentially difficult. Consider (in
FCM syntax):
#( boolean useIntegerArithmatic, Number value ) {
if ( useIntegerArithmatic ) { return value.longValue() * 2L; }
return value.doubleValue() * 2.0;
}
The correct return type is Number, but this is very hard to deduce.
With C3S (and potentially with CICE, but the concept isn’t
explicitly mentioned) there is no new syntax and instead Method1 is
used directly. This option avoids the difficulty of inferring the return
type. The equivalent “mouse” line in C3S and CICE is:
declare me = Method1< Void, MouseEvent >.method( e ) { ...; ... }; // C3S
Method1< Void, MouseEvent > me = Method1< Void, MouseEvent >( MouseEvent e ) { ...; ...; }; // CICE
Note on variance as used in BGGA and FCM
The above examples given for FCM and BGGA are slightly simplified. In practice
both proposals suggest variance is used to make the method types more general.
E.G. in FCM (but similarly for BGGA):
#( Number( Number, Number ) ) add = #( final Number lhs, final Number rhs ) { return lhs.doubleValue() + rhs.doubleValue(); };
Is translated into:
Method2< ? extends Number, ? super Number, ? super Number > add = new Method2< Number, Number, Number > () {
public Double call( final Number lhs, final Number rhs ) { return lhs.doubleValue() + rhs.doubleValue(); }
};
Note how the generic arguments to Method2 when declaring add use
variance. The idea is to increase the generality of the method, this is
a laudable aim.
11. Non-local return, break, and continue
C3S, FCM, and BGGA support non-local return, break,
and continue, whereas CICE doesn’t. In BGGA this facility is highly
emphasized whereas in C3S it is listed as a possible future option. In BGGA
it is considered a most
important feature and new syntax is introduced into closures for normal method
return and the normal method return syntax is used for a non-local
return (see point 7 above). It is hard to find a good usage example for non-local
returns because throwing an exception is a viable alternative; in fact they
are implemented by throwing an exception! Another point to note is that the
concept of non-local returns is only applicable in single threaded code in
which the evaluation order is well known (typically sequential). This style
of coding will be used less in the future because processors are all going
multi-core and therefore favour multi-threading. (This multi-core and hence
multi-threading for the future was noted in point 2 above.)
As already noted; a good example
is hard to find, but suppose that the collections library did not contain
binarySearch but did contain each
(and you didn’t want to use a loop!):
C3S
method Integer find( final Integer key ) {
each list, method( e ) {
if ( e.equals key ) { find.return e }
};
null
}
BGGA
Integer find( final Integer key ) {
for each ( final Integer e : list ) {
if ( e.equals( key ) ) { return e; }
};
return null;
}
(The most common justification for the non-local return
feature of BGGA using the traditional method return syntax is that it allows
easy refactoring of something like a for loop into an
each loop. It is true that this refactoring
is simple in BGGA, but I would suggest that the more common refactoring
of an inner class’s method/closure into a stand-alone method is now harder.
Therefore the use of normal method syntax for non-local return seems dubious.
The value you place on this feature would seem to depend on your programming
style. If you use a heavy procedural style then you might make extensive
use of blocks nested inside one another. However if you use an OO style or
a functional style you are much more likely to use a lot of small methods
instead and therefore hardly ever use a non-local return.)
In BGGA you can turn off non-local returns by making
the interface that the closure implements the method of extend
RestrictedFunction. It is not clear how practical this
technique of adding RestrictedFunction would be since
you can't retrofit to existing code easily.
Code for FCM would be similar to the above BGGA code and therefore isn't shown.
There is however a major point of difference between FCM and BGGA, in BGGA a
non-local return is allowed for all closures. In FCM non-local
returns are only allowed in control blocks.
In fact in FCM all return statements within a control block are non-local
returns and further more the return type of a control loop block is void
and therefore no confusion exists as to whether a return is local or not.
This difference concerning non-local returns considerably simplifies the FCM proposal.
Acknowledgements
Stefan Schultz and Stephen Colebourne (the authors of
FCM) both suggested that I write this blog. I asked the authors of the
respected proposal to check my examples of their code, thanks
to those who responded.
Nice job comparing the proposals. That said, I would prefer that Java not have closures at all and if it was to get them, I would prefer it to use the syntax that Groovy uses for closures.
Again, Java should not have closures. Java was designed with certain principles in mind (like same syntax as C/C++, etc.) and the more you stray from those principles, the more you just pollute the language into a monstrosity. If you want next-gen language constructs, use a next-gen language (like Groovy or Ruby). Personally, I would prefer Groovy to simply replace Java over time.
I think it is important that many views are canvassed and your view that Java should be left alone is quite ncommon. So I think the powers that be on the JCP should seriously consider doing nothing.
Good Job Howard, C3S looks very clean. Although I really like BGGA control invocation syntax, your expression looks cleaner over all.
I hope that when EG is formed, your participation will help ensure that Java remains a competitive language in the future such that our existing investment can be grown without reservation. (JCP should really expedite its community voting mechanism)
Aside from minor inconveniences, inner classes already provide much of the functionality of closures, therefore syntax should be be concise, clean and familiar as it would represent what appears to be the biggest improvement to the current means of abstraction at the method level.
Thanks for your comments. I agree with everything you said (including liking the control loop invocation syntax in BGGA, I have my reservations about the declaration syntax and about just how useful new control loops are). It would be good if some sort of consensus could be reached via the JCP.
I think the Ruby-like invocation syntax of C3S obscures the comparison a little. This omission of parens seems orthogonal to the closure-related modifications; for the purposes of comparing closure proposals, it would be more useful to see the C3S examples with the extra parens. Alternatively, you could try writing out all the proposals without the extra parens.
(BTW, I think removing the parens from Java is too radical a change. If you're willing to go that far, you might as well use Scala!)
I am hoping that one of the values of this comparison is to enable people to prioritize which of the separate concerns they like and which option for a given concern they like. So taking your example, you don't like option 7 Short Syntax or at least not the C3S options for short syntax. That is useful feedback on the proposals.
As an aside, the reason that I preferred making () optional over special syntax for a given control structure is that it achieves a similar level of conciseness and yet has many more uses cases. Personally I find all the () in Java cluttering and don't think they add to clarity much.
But as I said the purpose of the blog is to separate concerns and let people have their say.
> I am hoping that one of the values of this comparison is > to enable people to prioritize which of the separate > concerns they like and which option for a given concern > they like. So taking your example, you don't like option 7 > Short Syntax or at least not the C3S options for short > syntax. That is useful feedback on the proposals.
Actually, I was trying to say that your examples don't fully separate concerns. Most of the C3S examples (not just the ones in section 7) omit parentheses on method invocations.
Omitting parentheses on method invocations is a feature that is mostly orthogonal to the core closure-related issues (it could probably be added on to any of the other proposals). Therefore, I think it's best to keep that feature out of sight. The easiest way to do that is to change the C3S examples on this page to use parentheses on method invocations (except perhaps in the few cases where it is relevant).
> Personally I find all the () in Java cluttering and don't > think they add to clarity much.
I agree and that's one of the things I like about Ruby (and Scala). It's just that without them, it doesn't even look like Java code anymore :)
> Actually, I was trying to say that your examples don't > fully separate concerns. Most of the C3S examples (not > just the ones in section 7) omit parentheses on method > invocations.
Sorry for not getting your point first time. I did consider using standard Java except for the feature under discussion. For example, not using short syntax for inner classes/closures, point 1, except when discussing point 1. This would effect the presentation of all the proposals, not just C3S. In the end I decided it was best to show all examples, in each syntax, in what I considered the most likely way that example would be written in a particular syntax.
> Omitting parentheses on method invocations is a feature > that is mostly orthogonal to the core closure-related > issues (it could probably be added on to any of the other > proposals).
Yep I agree, it almost certainly a feature that could be added to the other proposals. It is a two way street though, for example the BGGA control loop syntax could be added to C3S. However I was documenting the proposals as they stand. Maybe you want to choose the variations you like and give some examples in another set of features.
> > Personally I find all the () in Java cluttering and > don't > > think they add to clarity much. > > I agree and that's one of the things I like about Ruby > (and Scala). It's just that without them, it doesn't even > look like Java code anymore :)
I didn't particularly push the no () point. One of the advantages in eliminating () is that it lets you make an embedded-domain-specific language more easily. Because you can make a library call look like a statement. Many of the Ruby libraries use this to great effect.