Neal Gafter and Peter von der Ahé
Building on lambda expressions and exception type parameters described in the companion document v0.6a, we describe support for control abstractions, which are APIs that can abstract patterns of control. These correspond to blocks and lambda expressions in languages such as Ruby, Smalltalk, and Scala.
We extend the syntax of a parenthesized expression to allow it to be preceded by a sequence of statements. The grammar for a parenthesized expression [JLS 15.8.5] is changed to:
- ParExpression:
- ( BlockStatementsopt Expression )
Note that the existing syntax for a parenthesized expression is a special case of this.
Meaning of Expressions: The specification for a parenthesized expression is modified to describe its new execution semantics: The block statements (if any) are executed in sequence, from left to right. The result of the block expression is:
Definite Assignment: The definite assignment rules for this construct are almost identical to that for the block statement. The definite assignment state before the first block statement is the definite assignment state before the parenthesized expression. The definite assignment state before the subexpression is the definite assignment state following the last block statement. The definite assignment state after the parenthesized expression is the definite assignment state after the contained expression.
Exception Checking: A parenthesized expression can throw exception type E if any statement or expression immediately contained in it can throw E.
Scoping: Local variables and classes declared by an immediately contained statement is in scope until the end of the parenthesized expression.
A new invocation statement syntax is added to make closures convenient for control abstraction:
- ControlInvocationStatement:
- foropt Primary ( FormalParameters : ExpressionListopt ) Statement
foropt Primary ( ExpressionListopt ) Statement
This syntax is a shorthand for the following statement:
Primary ( ExpressionList, # ( FormalParametersopt ) ( Statement (Void)null ) );
(See the later section loop abstractions for the meaning of the optional for keyword) This syntax makes some kinds of function-receiving APIs more convenient to use to compose statements.
We could use the shorthand to write our previous example (0.6a) this way
withLock(lock) { System.out.println("hello"); }
Another example of its use would be a an API that closes a java.io.Closeable after a user-supplied block of code, discarding any exception from Closeable.close:
<R, T extends java.io.Closeable, throws E> R with(T t, #R(T) throws E block) throws E { try { return block.invoke(t); } finally { try { t.close(); } catch (IOException ex) {} } }
We could use the shorthand with this API to close a number of streams at the end of a block of code:
with(FileReader in : makeReader()) with(FileWriter out : makeWriter()) { // code using in and out }
The specification supports writing methods that act as control statements, but when used to support loops the API implementor should be able to specify that the controlled statement should capture the meaning of break and continue in a way analogous to the built-in loop statements. For this purpose we allow the for keyword at the beginning of a control invocation statement (see above), and introduce the use of the keyword for as a modifier on method declarations:
- MethodDeclarator:
- foropt Identifier ( FormalParamterListopt )
An overriding or implementing method must be declared with the keyword for if and only if the method being overridden or implemented was declared with the keyword for.
A control invocation statement must use the for keyword if and only if the method being invoked was declared with the keyword for.
Within the controlled statement of a control invocation statement using the keyword for:
Example: The following illustrates a method used to loop through the key-value pairs in a map. The method
<K,V,throws X> void for eachEntry(Map<K,V> map, #void(K,V) throws X} block) throws X { for (Map.Entry<K,V> entry : map.entrySet()) { block.invoke(entry.getKey(), entry.getValue()); } }allows us to rewrite this
void test(Map<String,Integer> map) { for (Map.Entry<String,Integer> entry : map.entrySet()) { String name = entry.getKey(); Integer value = entry.getValue(); if ("end".equals(name)) break; if (name.startsWith("com.sun.")) continue; System.out.println(name + ":" + value); } }like this
void test(Map<String,Integer> map) { for eachEntry(String name, Integer value : map) { if ("end".equals(name)) break; if (name.startsWith("com.sun.")) continue; System.out.println(name + ":" + value); } }
Control transfers by return, break, or continue can, within an expression lambda, jump to a target outside the body of the lambda. It is possible for such code to be invoked at a time that the target of the transfer is not on the call stack of the current thread. In this case an exception of type UnmatchedTransferException is thrown.
package java.lang; /** * Exception thrown when a transfer from within a lambda doesn't have * a matching frame on the stack of the current thread. * * @author gafter */ public class UnmatchedTransferException extends RuntimeException { /** * This constructor is used by compiler-generated code. */ public UnmatchedTransferException(Jump jump); /** * Returns the thread in which the transfer target is executing. */ public Thread thread(); /** * Return the exception to be thrown to cause the transfer to * occur. This can be used to implement concurrent APIs * that support control transfers. See, for example, * http://gafter.blogspot.com/2006/10/concurrent-loops-using-java-closures.html * and http://markmahieu.blogspot.com/2008/08/for-eachconcurrently.html */ public RuntimeException transfer(); }
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.