Thursday 1 March 2007

Comparing closures - CICE, BGGA and FCM

I've been asked by a comment to compare the new FCM closure proposal from Stefan and myself with the other two proposals, CICE and BGGA. Obviously, this is difficult without being too biased, but I'll to do my best!

Its all about this

The key difference, when considering closures, between CICE and BGGA or FCM is the handling of this.

CICE is just a simplified syntax for creating an inner class. As such, the meaning of this is the same as in an inner class. As a reminder, in an inner class, this refers to the inner class instance, but there is implicit support for calling methods from the outer class. If you need to refer to this of the outer class you have to use the OuterClass.this syntax.

BGGA closures and FCM inner methods make this refer directly to the nearest surrounding class in the source code (the outer class). This results in much simpler code within the closure/inner method.

Ignoring this, the closure part of the three proposals vary principally by the detail of syntax. Thats why you have to focus on the semantics, not the syntax, when comparing them.

In addition, BGGA offers the control-invocation syntax for closures, while FCM offers method literals and invocable method references.

Example 1: Adding an ActionListener to a swing button

This is a fairly common example when using swing. The standard solution today is to use an inner class.

The following example is from Java 6. The method within the inner class can access handleButtonPress() via the special inner class rules:

  public void init(final JButton button) {
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent ev) {
        handleButtonPress(ev);
      }
    });
  }
  public void handleButtonPress(ActionEvent ev) {
    // actually handle the button press
  }

The following example is from the CICE proposal. This is shorthand for an inner class, so the method within the inner class can still access handleButtonPress() via the special inner class rules:

  public void init(JButton button) {
    button.addActionListener(ActionListener(ActionEvent ev) {
      handleButtonPress(ev);
    });
  }

The following example is from the BGGA proposal, and uses the standard-invocation syntax and closure conversion. The closure has full access to this (the instance of the init() method) and hence handleButtonPress():

  public void init(JButton button) {
    button.addActionListener({ActionEvent ev =>
      handleButtonPress(ev);
    });
  }

The following example is also from the BGGA proposal, but uses the control-invocation syntax. I believe that the authors of BGGA would not recommend that this syntax should be used for an ActionListener:

  public void init(JButton button) {
    button.addActionListener(ActionEvent ev : ) {
      handleButtonPress(ev);
    }
  }

The following example is from the FCM proposal, and uses an inner method. The inner method has full access to this (the instance of the init() method) and hence handleButtonPress():

  public void init(JButton button) {
    button.addActionListener(#(ActionEvent ev) {
      handleButtonPress(ev);
    });
  }

The following example is also from the FCM proposal, and uses an invocable method reference. This syntax merely references handleButtonPress() and FCM creates the bridging ActionListener. Note however, that the signature of handleButtonPress() must match that of the listener.

  public void init(JButton button) {
    button.addActionListener(this#handleButtonPress(ActionEvent));
  }

Example 2: A closure that multiplies a number

This example is using closures in the style of functional programming. This is rarely used in Java today, but can be simulated to a degree using an inner class.

The following example is from Java 6, and uses an inner class. Note how the button variable must be declared final:

  final int multiplier = 3;
  IntMultiplier mult = new IntMultiplier() {
    public int multiply(int value) {
      return value * multiplier;
    }
  };
  int result = mult.multiply(6);  // result is 18 (ie. 3 * 6)

The following example is from the CICE proposal. The only change is that the variable is now declared to be public (it could also be declared as final, or have no modifier which also means final):

  public int multiplier = 3;
  IntMultiplier mult = new IntMultiplier() {
    public int multiply(int value) {
      return value * multiplier;
    }
  };
  int result = mult.multiply(6);

The following example is from the BGGA proposal, and uses a closure. The {int => int} is a function-type that defines the parameters and return from the closure. The closure definition names the parameter and uses the value of multiplier from its environment. The result of the closure is returned by omitting the semicolon from the last line of the closure. The closure is called using the implied invoke() method:

  int multiplier = 3;
  {int => int} mult = {int value =>
    value * multiplier
  };
  int result = mult.invoke(6);

The following example is from the FCM proposal, and uses an inner method. The #(int(int)) is a method-type that defines the parameters and return from the inner method. The method definition names the parameter and uses the value of multiplier from its environment. The result of the method is returned using the return keyword. The closure is called using the implied invoke() method:

  int multiplier = 3;
  #(int(int)) mult = #(int value) {
    return value * multiplier;
  };
  int result = mult.invoke(6);

Summary

I've presented a quick comparison of CICE, BGGA and FCM with examples. Let me know if it was useful, and if you'd like more comparisons.

12 comments:

  1. Thanks for the review.

    I think method reference literals should be in Java, just to have a non-reflection (non-String) way of getting at them if needed. But here's something to watch out for if you do "closure conversion":

    ActionListener listener = this#handleButtonPress(ActionEvent);
    #(void(ActionEvent)) methodTyped = this#handleButtonPress(ActionEvent);
    // Now listener != methodTyped even though they look like they are a reference to the same thing.
    // I think this is why C# forced the "new DelegateType" syntax.

    Also, how do method types relate to java.lang.reflect.Method?

    Also, I think BGGA doesn't prohibit the ability to get a Method instance via reference literals (such as with your nicely proposed "this#handleButtonPress(ActionEvent)" syntax).

    Another comment: Single-abstract method classes might be more likely to want "this" to be the newly instantiated object. If you want "this" to be the outer class, then I think it's probably better to stick to interfaces like with BGGA. (Understanding that Object itself has some methods, but they'd be less tempting to use and therefore less likely to be confusing.)

    ReplyDelete
  2. Thanks Stephen, this helps a lot. I like FCM a lot but I'm not terribly comfortable with the method-type syntax with respect to the return. I understand the motivation to echo a function signature and leave things in their respective places, but I still find it a bit jarring on the examples here and in the proposal.

    One crazy idea I had was to reuse the return keyword to make it a bit more descriptive: #((int,String) return int) or even drop the parens around the params which seem unnecessary: #(int,String return int). In void cases, you could even drop the return altogether: #(int,String).

    ReplyDelete
  3. Not to completely plug my own blog, but I've been working on an informal mixins proposal, and FCM cleans it up quite a bit. I'd be very interested in your comments.

    Links:
    http://jroller.com/page/noah/?anchor=java_mixins
    http://jroller.com/page/noah/?anchor=java_mixins_revisited

    ReplyDelete
  4. Why not this #(int return int)? - though I find '#' not appropriate for Java

    #(int(int)) mult = #(int value){return value*multiplier;};

    simply lacks the symmetry of the BGGA variant, what about this:
    #(int{int}) mult = #(int value){return...
    or #( (int,double) ) for void with int and double args, etc.

    The '#' can be replaced by the '.' if we accept to reference no-args like toString() by toString(void). Omitting the () won't work as you'd collide with a variable/member toString.

    ReplyDelete
  5. Example 2 mentions "handleButtonPress" again. Copy/paste mistake?

    ReplyDelete
  6. Stephen Colebourne1 March 2007 at 13:07

    @Erik, No, not a cut-and-paste, I'm simply calling the method from example 1 (without copying it again and again into each example).

    @Tom, I see the concern over closure-conversion leading to objects not being equal. I'm just not sure its that important.

    Method types don't relate to j.l.r.Method. As we wrote the proposal we investigated various ideas of how to integrate them, but in the end we felt that the proposal was simpler and clearer without a link.

    As for whether to restrict to interfaces, we wanted to be able to handle the two cases from CICE - initValue() on ThreadLocal, and removeEldestEntry() on LinkedHashMap. CICE proposes a horrible hack of artificial abstract classes to allow these methods to be overridden.

    @Alex, #(int,String return int) is a syntax we did consider. In the end we went with a syntax that matches the order of things in a method declaration. This alternate syntax does have some appeal however.

    @Carsten, #(int{int}) isn't one we considered, and doesn't appeal to me right away. Your symmetry argument has some appeal though.

    @Damon, I've never knowingly had a need for currying, but obviously others do. As such we put it in the open issues section. If you've any ideas of a syntax that would work then let us know.

    @Noah, I'll try and reply on your blog when I have time :-)

    ReplyDelete
  7. I am confused. Why is "handleButtonPress" still relevant in example 2? It is only about multiplication. Right?

    ReplyDelete
  8. Stephen Colebourne1 March 2007 at 15:15

    @Erik, Sorry, I see it now!!! Its also fixed :-)

    ReplyDelete
  9. I like the concept, but please don't use '#'. The justification based on javadoc is unconvincing; javadoc is borrowing from the HTML syntax for an anchor reference and it doesn't make sense here.
    Borrowing from C, this syntax looks good:

    button.addActionListener(&handleButtonPress(ActionEvent));

    I'm not sure how you generalize that to inline functions, but maybe we don't need anything more. Writing a private method each time you need to do this isn't so bad and would be a big improvement over anonymous inner classes.

    If we actually want to do lambdas, the BGGA syntax is nicest so far.

    ReplyDelete
  10. @Brian, you cannot hand on private methods to a class outside of the defining one. Although allowing method references, one has to respect their accessibility. Not doing so would violate a core rule within Java and OO. Having inner methods, means are given for wrapping and passing (as the inner method will operate in the context it was constructed).

    ReplyDelete
  11. All these examples are too much typeing. In my opinion closures and type saftey are like oil and water. There are ways to make them mix, but ti takes a whole lot of effort, often too much. I want less typing, like

    Example 1

    button.actionPerformed = { handleButtonPress(it) }

    or alternately

    button.actionPerformed = &handleButtonPress

    Example 2
    int multiplier = 3
    mult = { it * multiplier }
    int result = mult(6)

    Dynamic languages have the advantage of allowing a lot of shortcutting when there is only one sensical way that a statement can be interpreted. The burden is placed on the compiler and not the end user hand encoding the mystic incantation of unreadable type code. These code samples are in Groovy BTW.

    ReplyDelete
  12. As far as I can see, none of these proposed changes actually introduces any new functionality. If I've got that right, then their only effect is to introduce new, alternate, spellings, for existing functionality. How can that possibly be a good thing?

    ReplyDelete

Please be aware that by commenting you provide consent to associate your selected profile with your comment. Long comments or those with excessive links may be deleted by Blogger (not me!). All spam will be deleted.