Closures for Java (v0.6a)

Neal Gafter and Peter von der Ahé

We describe a proposed extension of the Java Programming Language that supports deferred execution of snippets of code. These correspond to lambda expressions in languages such as Javascript, C#, and Groovy.

Lambda Expressions

We introduce a syntactic form for constructing an anonymus function value:

Expression:
ExpressionLambda
Primary:
StatementLambda
ExpressionLambda:
# ( FormalParametersopt ) Expression
StatementLambda:
# ( FormalParametersopt ) BlockStatement

We refer to these two expression forms, expression lambdas and statement lambdas as lambda expressions. Evaluating a lambda expression does not cause its controlled statement or expression to be evaluated immediately, but rather it builds a function object that represents the code. The function object can be stored and invoked later to cause the controlled statement or expression to be evaluated.

Within the controlled statement of a statement lambda

Within the controlled expression or statement

The type of a lambda expression is defined by the set of declared argument types, result expressions, and thrown types of the lambda's body. While the type of a lambda expression is not denotable in source, a lambda expression may be converted to some object type by a lambda conversion (see later). The target of such a conversion it typically a function type (see below) or other user-defined interface type. It is a compile-time error if a lambda expression appears in a context where it is not subject to a lambda conversion. The conversion occurs entirely at compile-time.

Example: the following lambda expression takes two integers and yields their sum:

#(int x, int y) x+y

Function Types

We introduce a syntactic form for a function type:

Type:
FunctionType
FunctionType:
# Type ( TypeListopt ) FunctionThrowsopt
TypeList:
Type
Type
, TypeList
FunctionThrows:
throws FunctionExceptionTypeList
FunctionExceptionTypeList:
ExceptionType
FunctionExceptionTypeList | ExceptionType

Informally, a function type describes the set of functions that accept a given list of argument types, result in a value of the given type, and may throw the indicated checked exception types.

Example: the following assigns to the local variable plus a function that computes the sum of its two int arguments:

#int(int,int) plus = #(int x, int y) x+y;

A function type is defined to have the same meaning as a parameterized interface type of a system-generated interface that has a single method named invoke. [This paragraph is a placeholder for a more precise description, including the name of the interface, required for interoperability among compilers. We expect the generated interfaces to be in the package java.lang.function, which will be closed to user-defined code, and the naming scheme to be an extension of the JVM method signature scheme.]

While some have proposed to add function types as a first-class extenstion to Java's type system, we don't believe that is necessary. Interfaces are expressive enough to be used to represent function types. Unfortunately, doing so directly in user code is clumsy, error-prone, verbose, and difficult to read and write. The function type notation provides a more readable, convenient, and less error-prone shorthand, and the interfaces to which it is translated provide a standard for interoperability among libraries and languages.

Example. The variable declaration

#int(String) parseInteger;

is translated into

interface java.lang.function.IO<A1,throws X> { // system-generated
    int invoke(A1 x1) throws X;
}
IO<? super String,? extends Nothing> parseInteger;
We would expect the compiler, runtime system, and debuggers to display these types using the function type syntax rather than the translation involving generics.

Because a function type is an interface type, it can be extended by a user-defined interface and it can be implemented by a user-defined class.

Our prototype translation scheme causes a function type

# Tr ( T0 ... Tn ) throws E0 | ... Em

to be defined as a system-generated parameterized interface type with a single abstract method

public Tr invoke(T0 x0, ... Tn xn) throws E0, ... Em

By relying on the existing Java subtype rules for generic interfaces, they obey the following subtyping relationship:

A function type # Tr ( T0 ... Tn ) throws E0 | ... Em is a subtype of function type # Ur ( U0 ... Ul ) throws X0 | ... Xk iff all of the following hold:

In English, these rules are

Lambda Conversion

A lambda expression may be converted to any compatible interface type by the lambda conversion, which is a kind of widening conversion.

There is a lambda conversion from a lambda expression to every interface type that has a single method m such that the lambda expression is compatible with m. A lambda expression is compatible with a method m iff all of the following hold:

Example: We can create a function object that adds two to its argument like this:

interface IntFunction {
    int invoke(int i);
}
IntFunction plus2 = #(int x) x+2;
 

Alternatively, using function types:

#int(int) plus2 = #(int x) x+2;
We can use the existing Executor framework to run a function object in the background:
void sayHello(java.util.concurrent.Executor ex) {
    ex.execute(#(){ System.out.println("hello"); });
}

Method References

A method reference is an expression form that allows a reference to a method to be used as shorthand for a lambda expression that invokes that method

Primary:
MethodReference
MethodReference:
Primary # TypeArgumentsopt Identifier ( TypeListopt )
When the Primary designates an expression (as opposed to a type) is treated the same as a lambda expression
(#(Type x0, Type x1 ...)tmp.<TypeArguments>Identifier(x0, x1 ...))
or
(#(Type x0, Type x1 ...){tmp.<TypeArguments>Identifier(x0, x1 ...);})

Where tmp is a temporary value that holds the computed value of the primary expression. The former translation is used when the resolved method has a non-void return type, while the latter is used when the resolved method has a void return type.

If the primary resolves to a type, then this is translated to

(#(Type x0, Type x1 ...)Primary.<TypeArguments>Identifier(x0, x1 ...))
or
(#(Type x0, Type x1 ...){Primary.<TypeArguments>Identifier(x0, x1 ...);})

when the resolved method is static, or

(#(Primary x, Type x0, Type x1 ...)x.<TypeArguments>Identifier(x0, x1 ...))
or
(#(Primary x, Type x0, Type x1 ...){x.<TypeArguments>Identifier(x0, x1 ...);})

when the resolved method is an instance method.

Exception type parameters

To support exception transparency (that is, the design of APIs in which the compiler can infer that exceptions thrown by a lambda expression are thrown by the API), we add a new type of generic formal type argument to receive a set of thrown types. [This deserves a more formal treatment]

TypeParameter:
throws TypeVariable TypeBoundopt
ActualTypeArgument:
throws ExceptionList
ExceptionList:
ReferenceType
ReferenceType | ExceptionList

What follows is an example of how the proposal would be used to write an exception-transparent version of a method that locks and then unlocks a java.util.concurrent.Lock, before and after a user-provided block of code. Given

public static <T, throws E extends Exception>
T withLock(Lock lock, #T()throws E block) throws E {
    lock.lock();
    try {
        return block.invoke();
    } finally {
        lock.unlock();
    }
}

This can be used as follows:

withLock(lock, #(){
    System.out.println("hello");
});

This uses a newly introduced form of generic type parameter. The type parameter E is declared to be used in throws clauses. If the extends clause is omitted, a type parameter declared with the throws keyword is considered to extend Throwable (instead of Object, which is the default for other type parameters). This type parameter accepts multiple exception types. For example, if you invoke it with a block that can throw IOException or NumberFormatException the type parameter E would be given as IOException|NumberFormatException. In those rare cases that you want to use explicit type parameters with multiple thrown types, the keyword throws is required in the invocation, like this:

Locks.<throws IOException|NumberFormatException>withLock(lock, #(){
    System.out.println("hello");
});

You can think of this kind of type parameter accepting disjunction, "or" types. That is to allow it to match the exception signature of a function that throws any number of different checked exceptions. If the block throws no exceptions, the type argument would be the type Nothing (see below).

Type parameters declared this way can be used only in a throws clause or as a type argument to a generic method, interface, or class whose type argument was declared this way too.

Throws type parameters are indicated in class file format by the presence of the character '^' preceding the type parameter.

Definite assignment

A variable V is (un)assigned after a lambda expression iff V is (un)assigned before the lambda expression

A variable V used in a lambda expression's body but declared outside the lambda is

The return statement

A return statement returns control to the invoker of the nearest enclosing method, constructor, or statement lambda.

The type Nothing

We add the non-instantiable type java.lang.Nothing. Values of this type appear where statements or expressions cannot complete normally. This is necessary to enable writing APIs that provide transparency for functions that do not complete normally. Nothing is a subtype of every obect type. The null type is a subtype of Nothing. At runtime, a NullPointerException is thrown when a value of type Nothing would appear. This occurs when a programmer converts null to Nothing.

Example: The following illustrates a function being assigned to a variable of the correct type.

interface NullaryFunction<T, throws E> {
    T invoke() throws E;
}
NullaryFunction<Nothing, Nothing> thrower = #(){ throw new AssertionError(); };

Reachable statements

The initial statement of a statement lambda is reachable.

An expression statement in which the expression is of type Nothing cannot complete normally.

Acknowledgments

The authors would like to thank the following people whose discussions and insight have helped us craft, refine, and improve this proposal:

C. Scott Ananian, Lars Bak, Cedric Beust, Joshua Bloch, Gilad Bracha, Martin Buchholz, Alex Buckley, Maurizio Cimadamore, Ricky Clarkson, Stephen Colebourne, Danny Coward, Luc Duponcheel, Erik Ernst, Rémi Forax, James Gosling, Christian Plesner Hansen, Cay Horstmann, Kohsuke Kawaguchi, Danny Lagrouw, Doug Lea, Peter Levart, "crazy" Bob Lee, Mark Mahieu, Niklas Matthies, Tony Morris, Martin Odersky, Terence Parr, Tim Peierls, Cris Perdue, John Rose, Ken Russell, Stefan Schulz, Guy Steele, Mads Torgersen, Zdenek Tronicek, Jan Vitek, and Dave Yost.

Minor changes since 0.6 first published

Changes in this revision (0.6) compared to BGGA 0.5

Changes that need to go into the prototype


Creative Commons License
Closures for Java by Neal Gafter and Peter von der Ahé is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.