Sunday 21 January 2007

Closures - Control-invocation syntax

The BGGA closures proposal includes a control-invocation syntax for calling closures. This is viewed as a convenient way to call some closure-control-methods (APIs). Unfortunately, the use cases envisaged in the proposal seem too limited to me.

Control-invocation syntax

All BGGA closures can be called like this (the standard-invocation syntax):

eachEntry(map, {String key, Integer value =>
  // do stuff with key and value
});

Some BGGA closures can be also called like this (the control-invocation syntax):

eachEntry(String key, Integer value : map) {
  // do stuff with key and value
}

The former is the more flexible, as it allows multiple closure-block parameters to be passed to a single API if needed. It is also the syntax that is closest to the dynamic languages like Ruby or Groovy.

I have concerns with the standard-invocation syntax in Java however. In particular, it is quite reminiscent of the inner class syntax. But with closures, return/break/continue may have a different meaning entirely. I also seem to always forget the part after the closing brace - ); - bracket semicolon.

Put simply, I believe that the control-invocation syntax fits stylistically into Java much better.

The current BGGA proposal places limitations on control-invocation syntax closures. They can't pass a result back to the closure-control-method. Nor can the closure-control-method pass a result back to the caller (I'm 99% sure of this). And they can only be used where the closure-control-method receives one closure. This is explained in BGGA thus:

This is not an expression form for a very good reason: it looks like a statement, and we expect it to be used most commonly as a statement for the purpose of writing APIs that abstract patterns of control. If it were an expression form, an invocation like this would require a trailing semicolon after the close curly brace of a controlled block. Forgetting the semicolon would probably be a common source of error.

So how to address these concerns? Well, here is a simple piece of code using the standard-invocation syntax:

  List[String] matches = eachMatching(list, {String str =>
    str != null
  });

So, to change to the control-invocation syntax we have to change the BGGA rules. Firstly, lets allow the closure-block to pass a result back to the closure-control-method using =>, as I talked about before. Since we've only changed one rule, we still can't return the matches directly.

  List[String] matches = new ArrayList[String];
  eachMatching(String str : list, matches) {
    => str != null;
  }

Secondly, lets allow the closure-control-method to return a result, but without being an expression, and without requiring the semicolon.

  List[String] matches = eachMatching(String str : list) {
    => str != null;
  }

The eachMatching result can only be used to assign to a variable. You cannot use the dot operator to call a method such as size() on the block. (Formally, this is now a new form in the language, neither a statement or an expression, but a restricted type of statement where the {} block has taken the place of the semicolon.)

Now, taking this on to the final level we could add in the 'for' keyword to identify that a closure is occurring, to complete the picture.

  List[String] matches = for eachMatching(String str : list) {
    => str != null;
  }

Well, some of you probably hate this last snippet of code, but I hope some might also see beauty there. It is a mix of keywords and statements in a way that definitely isn't quite like Java of today. And yet, its also I think its clearly derived from today's syntax. What I do hope however, is that you can understand it from a simple glance.

As usual, let me know your feelings on this syntax direction for closures.

8 comments:

  1. I'm not sure, where to put this (too many Closure-posts), hence I will take the latest. Instead of poking around with return, why not change the proposal's syntax for defining closures and function types slightly to allow for returning values by using a different keyword or key symbol? In the current proposal there already is such a symbol: a fat arrow. Although, I would rather like a keyword (e.g., yield), the => sign seems shorter and more welcome for some people.

    Below I wrote down the current style in BGGA and a modified variant with several examples, which makes the => symbol to become a Closure-Yield. I'm not sure whether it becomes less readable or less Java-ish or more difficult to parse. The only change I made is dropping some curly braces, adding some parens and shifting the fat arrow.

    === BGGA proposed style (° = opt) ===
    Closure:
    ´ { FormalParameterDecls° => BlockStatements° Expression° }
    FunctionType:
    ´ { Types° => Type Throws° }

    Example:
    {int=>int} plus2 = {int x => x+2};

    === Modified style (° = opt) ===
    Closure:
    ´ ( FormalParameterDecls° ) { Statements° }
    Statements:
    ´ Statement; Statements°
    Statement:
    ´ BlockStatement
    ´ => Expression
    (Not sure, how to denote that the usage of => follows the same rules than return in a method.)
    FunctionType:
    ´ ( Types° ) => Type Throws°

    Definitions:
    (int) => int plus2 = (int x) => x+2;
    (List, T) => boolean contains = (List list, T match) {
    ´ for (T t : list) {
    ´ ´ if (match.equals(t)) => true;
    ´ }
    ´ => false;
    }
    (List, (T) => boolean) => List findAll = (List list, (T) => boolean condition) {
    ´ List matches = new ArrayList();
    ´ for (T t : list) {
    ´ ´ if (condition.invoke(t)) {
    ´ ´ ´ matches.add(t);
    ´ ´ }
    ´ }
    ´ => matches;
    }

    Usages:
    i = plus2(j);
    b = contains(names, "Neal");
    // argument style
    List matches = findAll(bookingList, (Booking booking) {
    ´ for (Purchase purchase : booking.getPurchases()) {
    ´ ´ if (purchase.contains(typeToFind)) {
    ´ ´ ´ => true;
    ´ ´ }
    ´ }
    ´ => false;
    });
    // control invocation style
    List matches = findAll(Booking booking : bookingList) {
    ´ for (Purchase purchase : booking.getPurchases()) {
    ´ ´ if (purchase.contains(typeToFind)) {
    ´ ´ ´ => true;
    ´ ´ }
    ´ }
    ´ => false;
    }

    ReplyDelete
  2. Stefan: the reason we don't allow this is explained here: http://gafter.blogspot.com/2006/08/tennents-correspondence-principle-and.html

    ReplyDelete
  3. Further to Neal's comment, the spec already uses => to denote the start of a closure body, therefore using it for a return value would be odd - would you have two => symbols in a multi-statement closure?

    I'd prefer one syntax, the 'statement' form, for both statement and expression closures. E.g.:

    List[String] onlySmiths=filter(String name:names)
    {
    name.contains("Smith")
    };

    For the above, I don't think last-line-no-semi-colon is a bad thing. Neal says that the issue is that there's a semi-colon at the end of that, but I'm not sure why that's an issue; in a statement context, don't require a semi-colon. Otherwise, do. Perhaps there's something subtle I can't see here.

    ReplyDelete
  4. @Neal: Thanks for that reference. So I understood, that the problem would be for (somehow) nested returns (or ^ or =>) and, maybe, too, for break and continue? I think, that thinking closures may be quite unsatisfying sometimes ;)

    Having a closure's value being the value of the last statement within the closure is actually the same as in Smalltalk, so I should not wonder. But in general, the problem seems to be having a generic return statement.

    So for returning a value, one would, for example, define a temporary variable, which will be used as last statement in the closure. Maybe it would make things clearer, if a return variable can be defined that will be used to identify the return value of the closure that is the scope of this variable. And as markup that the assigment will end the closure immediately, use =| instead of = (looking like a terminal symbol to not conflict with FunctionType-Definitions). Somewhat like follows:

    List matches = findAll(Booking booking : bookingList) {
    ´ | boolean foundOne |
    ´ for (Purchase purchase : booking.getPurchases()) {
    ´ ´ if (purchase.contains(typeToFind)) {
    ´ ´ ´ foundOne =| true;
    ´ ´ }
    ´ }
    ´ foundOne =| false;
    }

    Hiding names would be easy to identify and nested closures can use seperate names as for arguments.

    @Ricky: That's why I changed the => to point to the return value not the closure body and have the closures arguments defined as one is used to for methods: using parentheses. Which would also be consistent with defining FunctionTypes, btw.

    ReplyDelete
  5. Stephen Colebourne22 January 2007 at 00:22

    @Stefan, In your first comment you changed the definition syntax but ended up with the same usage syntax as I'm proposing. I don't believe that the current BGGA definition syntax needs to change at all to achieve the usage syntax I'm suggesting with a => operator meaning 'resulting in'.

    Your second proposal is not unlike one possibility I've considered before. It definitely identifies the closure to return to which is a good thing. I'm not sure that exact syntax looks good though.

    @Ricky, I said in the last post that for a single expression closure-block, there is no need for two => operators immediately after one another - they merge into one (thats why I'm using =>). This fits with the single line style.

    And we are not that far apart on the actual format either, as compared to your example, I'm asking for:

    List[String] onlySmiths = filter(String name:names) {
    ' => name.contains("Smith");
    }

    ReplyDelete
  6. @Stephen: It's similar wrt. the control invocation syntax. The closure infix uses the fat arrow already to seperate the closure argument definitions from the closure block, which would conflict with using the arrow for returning a value. That's what I tried to capture in a consistent way. Unfortunately, as Neal referenced, there is no way for having a generic return value statement.

    To the terminal Symbol, I actually didn't think much about the notation but rather needed some way to describe the idea. I borrowed the syntax for describing temporary/local block variables in Smalltalk and took symbol that does not conflict with the FunctionType definition and still reminds of the assignment.
    In general, it's nothing but a typed label put on the closure.

    ReplyDelete
  7. "Secondly, lets allow the closure-control-method to return a result, but without being an expression, and without requiring the semicolon."

    Wow! Now I got it!

    Made the hair on the back of my neck stand up.

    ReplyDelete
  8. Stephen Colebourne23 January 2007 at 00:35

    "Made the hair on the back of my neck stand up."

    I hope thats 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.