Friday, December 28, 2007

Local Variables

Access to Local Variables

So far, closures were a concise analogy to anonymous inner classes. But they offer more. In inner classes, you can access a variable from enclosing scope only if it is final. For closures, there is no such restriction:

  public static void main(String[] args) {
    int x = 4;
    { => void } printX = 
      { => System.out.println(x); };  // x is a free variable here
    x++;
    printX.invoke();  // will print 5
  }
  

In function printX, x is a free variable because it is not declared in the printX's block of code (it is neither a parameter nor a local variable in this block). We can access and change free variables:

  int x = 100;
  { => int } nextX = { => ++x };
  System.out.println(nextX.invoke());  // will print 101
  System.out.println(nextX.invoke());  // will print 102
  x = 200;
  System.out.println(nextX.invoke());  // will print 201
  

A block of code is packed with variables of the enclosing scope, i.e. the scope where the block is declared. This is where the name "closure" comes from. For example, the following block of code will always work with variable x in the main method:

  static void doTwice({ => void } block) {
    block.invoke();  // will print 10
    int x = 20;
    block.invoke();  // will print 11
    System.out.println(x);  // will print 20
  }
  
  public static void main(String[] args) {
    int x = 10;
    // the block is "packed" with variable x
    doTwice({ => System.out.println(x++); });  
  }
  

A closure can use variables of the enclosing scope even if this scope is not active at the time of closure invocation. For example, a closure can use local variables of a method after return from this method:

  static { => int } makeSum() {
    int n = 1, s = 0;
    // the following closure uses local variables n and s
    return { => s += n; n++; s };
  }
  
  public static void main(String[] args) {
    { => int } sum = makeSum();
    System.out.println(sum.invoke());  // will print sum 0 + 1
    System.out.println(sum.invoke());  // will print sum 0 + 1 + 2
    System.out.println(sum.invoke());  // will print sum 0 + 1 + 2 + 3
  }
  

If we declare n and s local in a closure, we will get different results:

  static { => int } makeSumWrong() {
    return { => int n = 1, s = 0; s += n; n++; s };
  }
  
  public static void main(String[] args) {
    { => int } sum = makeSumWrong();
    System.out.println(sum.invoke());  // will print sum 0 + 1
    System.out.println(sum.invoke());  // will print sum 0 + 1
    System.out.println(sum.invoke());  // will print sum 0 + 1
  }
  

Variables n and s are now local in a closure, so they are initialized upon each invocation of this closure.

Reference this in a closure has the same meaning as this in enclosing scope.

  public class TestThis {
    String s = "hello";
    void test() {
      String s = "hi";
      { => System.out.println(this.s); }.invoke();  // will print "hello"
    }
    public static void main(String[] args) {
      new TestThis().test();
    }
  }
  

Implementation Issue

Local variables that are accessed from a closure are allocated on the heap, so that they are available even when we exit the block they are declared in.


Exercises

  1. Determine the output.
      class ClosureWithThis {
        int x = 1;
        void tryThis() {
          new Invoker().tryThis({ int x => 
            System.out.println(x);
            System.out.println(this.x);
          });
        }
        public static void main(String[] args) {
          new ClosureWithThis().tryThis();
        }
      }
      
      class Invoker {
        int x = 2;
        void tryThis({ int => void } p) {
            p.invoke(3);
        }
      }
      
  2. Determine the output.
      static void m({ => int } c1, { => int } c2) {
        System.out.println(c1.invoke());
        System.out.println(c2.invoke());
      }
    
      public static void main(String[] args) {
        int i = 1;
        m({ => ++i }, { => --i });
      }
      

Solutions

  1. x refers to the argument and this.x refers to field x of the ClosureWithThis instance. Thus, the first println will print 3 and the second will print 1.
  2. Closures { => ++i } and { => --i } refer to the same variable i. Thus, the first println will print 2 and the second will print 1.

10 comments:

Tim Büthe said...

Hi,

you wrote In inner classes, you can access a variable from enclosing scope only if it is final. For closures, there is no such restriction

You are right, closures don't have this restriction and inner classes had it, but while playing around with the latest version (closures-2008-03-22) i realized that inner classes can access non-final variables also.

So what is this about? Will this restriction for inner classes be dropped? And if not, why can you get it right for closures and not for inner classes?

regards,
Tim

Zdeněk Troníček said...

Hi Tim,

I do not expect this restriction will be dropped. Due to compatibility it should be preserved.

Tim Büthe said...

well, all the old code would still be compilable, so this would be compatible. And why should inner classes not be able to use shared variables (@shared annotated)?

I don't found a word about that in neals blog, neither in yours, so i ask myself if this is change by fault or intentionally.

Zdeněk Troníček said...

Hi Tim,

Neal Gafter wrote me: "I think it makes sense to give anonymous inner classes the same behavior as restricted closures."

So, you can forget about my previous comment :o).

Anirudh Vyas said...

How is closure implemented? I mean in context of types; are closures of different type? and how does Java handle the difference between the namings, a function type name and a method name ...( wont there be a namespace collision?)

Regards
Vyas, Anirudh

Zdeněk Troníček said...

Hi,

closures are implemented as anonymous inner classes (see my post "Closures" from December 2007).

Anonymous said...

Good Job! :)

Anirudh Vyas said...

I compiled a simple closure code ... doing something with outside variable,
public final class SimpleClosure {

public static final void main(final String[] pArgs){
// non final.
int y = 4;
{int => int} funClosure = {int x => ++y; x=x+y};
funClosure.invoke();
sysout("{" + funClosure.invoke() + "});

However with this code, I get a warning:
warning: [shared] captured variable y not annotated @Shared,

So I put on the annotation, Can this be reflected on the post ... Nice post! I am still a little confused on semicolon (where to put it ... etc). Will have to read up a little more I guess :)

Regards
Vyas, Anirudh

Zdeněk Troníček said...

Hi Anirudh,

the Shared annotation was added later to the prototype. It is described in the version-2008-02-22 post.

Paypercall said...

plumbing company near me