Brian's Waste of Time

Sun, 16 Jan 2005

Re: Groovy Closures and Related Syntax Issues

Mike Spille has been putting his money where his mouth is in regards to Groovy, and it is definately a good thing to get good feedback =)

I want to expand on something which he touches on, but then munges up with another concept -- closures and anonymous functions. Closures are *lexical* in nature. A closure is a closure over a lexical scope. Lexical scope is the text of the program, not the stack it is evaluating in. Mike's explanation describes lexical scope more along the lines of dynamic scope (which is a whole other beast, only commonly encountered in elisp) intersecting with lexical scope. Mike explains the concept well, but then forgoes picking on Groovy for ramming the the two together when talking about them, and accepts them as is. Maybe Mike is nicer than me in that regard, I'm am going to pick a nit in this regard and seperate them.

You can achieve (limited) lexical closures in in Java using anonymous inner classes. Swing's event model is predicated upon them, even. Take the following:

  final Object flag = new Object();
  final int[] jobs_queued = { 0 };
  
  for (int i = 0; i < job_count; ++i) {
      Job job = new Job(new Runnable() {
          publc void run() {
              doSomethingLongAndAnnoying();
              jobs_queued[0]--;
              synchronized (flag) {
                flag.notify();
              }
          }
      });
      for (;;) {
          try {
              executor.execute(job);
              jobs_queued[0]++;
              break;
          } catch (InterruptedException e) {}
      }
  }
  blockUntilCompleted(jobs_queued, flag);

The above code creates a Job and passes it to some asynchronous executor (actually using Doug Lea's util.concurrent package, with its InterruptedExceptions =) Now, this job takes as an argument an instance of Runnable, in this case implemented as an anonymous inner class. The scope of the run method in this Runnable encloses the scope in which it is defined. It accesses and even modifies variables (final ones, anyway) in the local scope of the method body in which it is defined (jobs_queued).

Now, note that this runnable is going to be executed in a seperate thread, in a different stack, and will still modify items in this scope. The scope of the run method encloses the scope in which it was defined.

Where closures get really interesting is in defining anonymous functions -- something that Lisp, Python, Ruby, Groovy, C#, etc all make really easy, and Java doesn't allow (anonymous classes with a single method yes, just the function, no). This is where you get into functional programming styles, which Java allows for, but not in a syntactically pleasent way (like doing objects C -- there are multiple ways of doing it, but none are really as nice as languages with a syntax designed to make it nice (Java, for instance)).

A lot of groovy's anonymous function (called closures in groovy space, something which needs to be changed imho) syntactic sugar stems from emulating the awesome power of Ruby's blocks while not having the block concept. Mike rails at some of the language oddities introduced by having all blocks be anonymous functions, rather than just be blocks. Ruby's blocks are not anonymous functions when created:

irb(main):001:0> foo = { puts "hello world" }
SyntaxError: compile error
(irb):1: syntax error
foo = { puts "hello world" }
              ^
(irb):1: syntax error
        from (irb):1
irb(main):002:0> 

But they can be used to create anonymous functions very easily:

irb(main):004:0> foo = proc { puts "hello world" }
=> #
irb(main):005:0> foo.call
hello world
=> nil
irb(main):006:0>

Blocks are their own thing in Ruby, and are used all over the place. The most visible is as a special argument to a function different from other arguments:

irb(main):011:0> 5.times { |i| puts i }
0
1
2
3
4
=> 5
irb(main):012:0> 

Interestingly, the block, { |i| puts i } is not an argument to the times call. It is a block passed in, as can be seen most easily by looking at the error message if you skip it:

irb(main):012:0> 5.times
LocalJumpError: no block given
        from (irb):12:in `times'
        from (irb):12
        from :0
irb(main):013:0> 

Ruby allows the passing of a block to any function, the function may or may not *do* anything with it. It is a special argument which can be passed. This may seem like a bizarre special case, but it is actually wonderful when used in practice (it probably drives python people nuts, though, as it is too pragmatic ;-)

Now, Ruby and Groovy share a lot of similarities, and having the concept of blocks is one of them, though they implement it quite differently, and that makes for some semantic differences as well. Groovy does not have any kind of implied block parameter to functions, you must explicitely declare passed blocks as a param (though you don't need the &foo "this is a function" stuff). A bare block is a valid expression which returns an anonymous function.

This may be nice, and is certainly a but easier to digest as there is a definate stumble when people start writing functions in Ruby which accept blocks (wait, I yield to invoke it? huh?). It feels natural when you get it, but maybe that is because you just accept it, and passing a block as a formal param seems reasonable (though it is tricky as for syntactic nicety you want it to be last, but if you have a variable number of args, deciding if it is the final arg, or a block is... ick -- I do like Matz's solution, but it certainly is not the only way).

Groovy is strongly object oriented, but it is also strongly functional. It really encourages you to pass functions around -- and makes it easy to munge scopes. Building a lot of the core language syntax around support for this is a design decision, and a decision I have actively supported in the past, and will continue to in the future. It is good design, even if it is different from Java. You certainly don't have to use it, but it allows for much more expressive (expressive != terse) programs, and that is very good.

2 writebacks [/src/groovy] permanent link