Understanding the closures debate

Does Java need closures? Three proposals compared

1 2 3 4 5 Page 4
Page 4 of 5

Non-local control statements

Naturally, the simple examples above only scratch the surface of what closures are about. Covering all of the issues related to each proposal is beyond the scope of this article. Moreover, in most aspects the proposals are relatively similar in what they propose -- but there is one area where the proposals differ drastically.

In the BGGA proposal it is permissible for a closure to contain non-local control statements that affect the control flow of the context in which the closure is defined, not only the control flow within the closure itself. Listing 10 shows an example of a non-local return statement. We use our earlier example, namely the forEach method that applies a closure's functionality to all elements in a sequence. The closure prints the argument it receives and contains a (non-local) return statement that terminates the main method as soon as the argument equals 3.

Listing 10. A non-local return statement in action

class Utils {
      public static <T> void for<each(Iterable<T> seq, {T => void } fct) {
        for (T elm : seq)
        fct.invoke(elm);
      }
    }

    class Test {
      public static void main(String[] args) {
        List<Integer> nums = Arrays.asList(1,2,3,4,5);
        {Integer => void } print = { Integer arg ==> if (arg == 3) return; System.out.println(arg); };
        Utils.forEach(nums,print);
      }
    }


The return statement in the closure does not only return from the closure, but also terminates the main method where the closure was defined. The effect is that with this closure our sample program terminates as soon as a sequence element equal to 3 is encountered; that is, it would print 1 2 and then stop.

The introduction of non-local control statements distinguishes BGGA from the CICE and FCM proposals. In both CICE and FCM a return statement simply means "return from the closure." The effect of corresponding closures in CICE and FCM would be that the closure returns as soon as a sequence element equal to 3 was encountered, but the forEach method as well as the main method would keep going -- so the program would print 1 2 4 5.

BGGA requires non-local return statements because the proposal aims to enable us to factor out common control structures by means of closures -- something we'll demonstrate further below. The idea is that a closure behaves like a block of code that relates to the scope in which it is defined. That is, the closure can both access variables of the enclosing scope and affect the control flow of the enclosing scope, for instance by saying: "return from the enclosing method."

Lexical binding

Lexical binding is the principle behind features like the non-local return statement. BGGA closures apply lexical binding to the following:

  • Variables from enclosing scopes
  • The meaning of this
  • The meaning of break, continue, and return

Lexical binding of variables in a closure means that the closure can access variables from the enclosing scope. That's not a new idea in Java. Local and anonymous classes have a similar feature: they can access final variables of the enclosing scope. In a closure it is similar; just the restriction that the variable must be final goes away. Instead, you may qualify the variable with a @Shared annotation (at least in the BGGA proposal; the other two proposals allow access without final or any other qualification).

Lexical binding of the this keyword is new in Java, however. In anonymous inner classes, any use of the keyword this refers to the instance of the inner class, not to an instance of the enclosing class. The enclosing class's this is referred to using different syntax, namely EnclosingType.this. CICE will stick to this tradition, BGGA and FCM will break with it. In BGGA and FCM, the keyword this will be lexically bound and will automatically refer to an instance of the enclosing type in which the closure is defined.

The fundamental difference between the proposals comes with lexical binding of control statements. In BGGA, not only the return statement but also the control statements break and continue are lexically bound. Only BGGA proposes this feature; CICE and FCM do not have it.

The BGGA proposal needs the lexical binding of control statements in order to ease the factoring out of common code. In contrast, the emphasis of the FCM proposal in not primarily on refactoring, but more on enabling a functional programming style where functions and functionality (in the form of closures) are well supported. As a result, the FCM proposal has lexical binding for variables and this, but not for break, continue, and return -- and for a good reason: non-local return statements have their pitfalls, as we will see shortly.

Returning a value from a BGGA closure

You might wonder how the compiler distinguishes between a local and a non-local return statement in a BGGA closure. The answer is that there is no local return statement in BGGA. A return statement always returns from the nearest enclosing method or constructor in which the closure is called. If a closure needs to return a value it does so without an explicit return statement. The last expression in a closure's body always serves as an implicit local return statement. For illustration, here is a BGGA closure that returns a value:

Listing 11. Returning a value from a BGGA closure

{double => double} f = {double x =>
      double res = Math.log(x);
      System.out.println("log of "+x+" is "+res);
      res // implicit local return statement
      };

The lexical binding of the control statements break, continue, and return adds quite a bit of complexity to the BGGA proposal. We won't mention all of the complications, but just consider the non-local return statement. What if in our example the closure is not executed in a function that is invoked from main, but in a function that is executed in a different thread? In that situation "return from main" does not make any sense, because the other thread's call stack does not have a main method.

The BGGA proposal addresses this situation by raising an unchecked exception at runtime. In addition, something must be done to catch this kind of problem at compile time. BGGA would add a marker interface or some kind of syntax distinguishing restricted from unrestricted closures, where restricted closures are those that are not allowed to be executed in a different thread. We won't go into further details, but suffice it to say that the fundamental problem remains: lexical binding of control statements can be error prone.

1 2 3 4 5 Page 4
Page 4 of 5