Sunday, October 30, 2005

Closure, But No Cigar

To my knowledge, closures aren't even on the drawing board yet let alone scheduled for a particular JDK release, so I thought I'd see what we can do in the mean time (apart from switching to Ruby ). Don't get your hopes up though. The results are interesting but far from ready for prime time. First, let's start with a pre-closure example. Our program iterates over a series of numbers and invokes a Runnable to add them up (we could obviously implement this inline, but let's keep the example simple):
static int sum;

public static void main(String[] args) {
  for (int i = 0; i < 10; i++) {
    final int it = i;
    Runnable r = new Runnable() {
      public void run() {
        sum += it;
      }
    };
    r.run();
  }
  System.out.println(sum);
}
For starters, I'd like to get rid of the run method declaration. The Runnable constitutes five lines of code of which one is important; that's 80% clutter. My new class Closure [ab]uses instance initializers, a lesser known language feature, as a substitute for the run method. To execute the closure, I simply instantiate its class behind the scenes. This approach eliminates some boilerplate and even a couple lines of code:
static int sum;

static class Adder extends Closure<Integer> {{
  sum += it;
}}

public static void main(String[] args) {
  for (int i = 0; i < 10; i++) {
    Closure.execute(Adder.class, i);
  }
  System.out.println(sum);
}
Unfortunately, you have to implement the closure using a static nested class which means you must explicity pass in all state via the closure argument it. If you create another nested class so you can pass in multiple arguments, you undo what little good this has done. Ideally, we'd use an inner class instead. Inner classes can appear inline in methods and see final local variables. There's no reason we couldn't instantiate an inner class as we do the static nested class, however, short of bytecode manipulation, I know of no way to get the method's local variables and the outer instance to pass to the inner class's constructor. Even if we could somehow scale this inner class wall, I don't feel the end justifies the means: one or more object creations, some thread local lookups, and unorthodox, most likely confusing syntax. That said, I give you Closure:
package org.crazybob.closure;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

public abstract class Closure<T> {

  protected T it;

  private static ThreadLocal<List> tlStack =
      new ThreadLocal<List>() {
        protected List initialValue() {
          return new ArrayList();
        }
      };

  protected Closure() {
    List stack = tlStack.get();
    this.it = (T) stack.get(stack.size() - 1);
  }

  /**
   * Executes a closure with the given argument.
   */
  public static <T> void execute(
      Class<? extends Closure<T>> closure, T it) {
    List stack = tlStack.get();
    try {
      stack.add(it);
      execute(closure);
    } finally {
      stack.remove(stack.size() - 1);
    }
  }

  private static <T> void execute(
      Class<? extends Closure<T>> block) {
    try {
      Constructor c =
          block.getDeclaredConstructor();
      c.setAccessible(true);
      c.newInstance();
    } catch (IllegalAccessException e) {
      throw new RuntimeException(e);
    } catch (NoSuchMethodException e) {
      throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
      throw new RuntimeException(e);
    } catch (InstantiationException e) {
      throw new RuntimeException(e);
    }
  }
}

2 Comments:

Anonymous Anonymous said...

Bob,
What's your take on Smalltalk in this context? What is missing? Is it just about their threading model? I, personally, have a feeling that Ruby is not quite there yet, but it's becoming more popular. :-) I didn't intend to start another flames war, just want to hear an opinion of a knowledgable person with a great taste for software (Dynaop, anyone?) Well, don't take it as a groundless flattery: it's just MHO.
Apologize if you find this quetion too inflamatory.

6:05 PM  
Blogger Bob said...

That's not inflamatory at all. I actually don't have any real experience with Smalltalk outside of reading _Smalltalk Best Practice Patterns_ to learn OO. I've heard nothing but good things from friends who used to use it though.

I don't doubt I'd love Smalltalk. It has mixins, closures, etc., right? But, Java pays the bills. As to why Java continued to grow while Smalltalk use declined, I guess Java just has more universal appeal. It doesn't surprise me. Developers who truly appreciate and utilize OO constitute a small minority (from my experience).

8:28 PM  

Post a Comment

<< Home