The Artima Developer Community
Sponsored Link

Pattern Centric Blog
Comparing Inner Class/Closure Proposals
by Howard Lovatt
April 11, 2007
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.

Table 1: “At a Glance” Comparison
Feature C3S FCM CICE BGGA
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

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.

C3S

 
    declare ls = ... ;
    sort ls, method( s1, s2 ) { s1.length - s2.length };

FCM

    List< String > ls = ... ;
    sort( ls, #( String s1, String s2 ) { return s1.length() - s2.length(); } );

CICE


    List< String > ls = ... ;
    sort( ls, Comparator< String >( String s1, String s2 ) { return s1.length() - s2.length(); } );

BGGA

    List< String > ls = ... ;
    sort( ls, { String s1, String s2 => s1.length() - s2.length() } );

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:

    Integer sum() {
      return reduce( ls, 0, #( Integer total, Integer e ) { return total += e; } );
    }

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.

C3S

    time new {
      method Integer call { ...; ... }
      method toString { "Method1" }
    }, new {
      method Integer call { ...; ... }
      method toString { "Method2" }
    };

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:

    sort ls, method( s1, s2 ) { s1.length - s2.length };

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.:

    Boolean someMethod() {
      return invert( { => true } );
    }

(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:

    method Boolean someMethod {
      return invert method { true };
    }

(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.

Talk Back!

Have an opinion? Readers have already posted 9 comments about this weblog entry. Why not add yours?

RSS Feed

If you'd like to be notified whenever Howard Lovatt adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Dr. Howard Lovatt is a senior scientist with CSIRO, an Australian government owned research organization, and is the creator of the Pattern Enforcing Compiler (PEC) for Java. PEC is an extended Java compiler that allows Software Design Patterns to be declared and hence checked by the compiler. PEC forms the basis of Howard's 2nd PhD, his first concerned the design of Switched Reluctance Motors.

This weblog entry is Copyright © 2007 Howard Lovatt. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use