Tuesday, 2 January 2007

Closures use cases - updating

More 'use cases' for the closures proposal (see yesterday). This time, the more thorny subject of methods that alter the collection.

The standard solution to altering a collection using a closure in other languages seems to be 'don't do it'. Instead, functional programming prefers returning a new collection with the contents altered. I would argue that while that might sometimes be what you want, it probably doesn't fit well with the Java paradigms. ie. in Java, we tend to alter the state of our existing objects. Lets see how that works:

filter(Collection)

  Collection[String] coll = ...
  filter (String str : coll) {
    boolean remove = false;
    if (str == null) {
      remove = true;
    }
    remove
  }

This operation would remove the element (calling Iterator.remove) if the result of the closure is true. But, the syntax is very clumsy. The result can only be specified as the last expression in the block, and it has to be without a semicolon. This looks like a guaranteed cause of confusion.

transform(List)

  List[String] list = ...
  transform (String str : list) {
    str.toUpperCase()
  }

This operation would change each value in the list to a new value, based on the result of the closure. Note again that the result is defined by a statement with no semi-colon at the end. In this case, because there is no other statement in the block, this doesn't look too bad, but I'm sure I'd put the semi-colon in as a reflex action.

Alternate approach 1 - yield parameter

  List[String] list = ...
  update (String str : list : boolean removeItem) {
    if (str == null) {
      removeItem = true;
    }
  }

The idea here is that instead of yielding a result back to the closure by some arbitrary last statement without a semi-colon, a variable is provided which must be updated with the resut of the code-block. In other words, removeItem is the result of the closure, and you have to assign a value to it

There may be ways to play with the syntax, but this does feel a little 'odd'. It would work as a general purpose replacement for the 'last-line-no-semi-colon' concept.

Alternate approach 2 - an updater

  List[String] list = ...
  update (String str, Updater[String] it : list) {
    if (str == null) {
      it.remove();
    } else {
      it.set(str.toUpperCase());
    }
  }

The idea is to provide the closure with an object suitable for updating the list. Now perhaps this could just be a ListIterator, or it might be a separate class with a different set of methods (ie. 'Updater') - thats detail.

The nice part about this solution is that it is closer to traditional Java syntax and traditional Java mentality. Of course you might then ask exactly what this loop is gaining over the current code. One downside is that the 'String' parameter has to be repeated. This could potentially be solved, but with its own issues:

  List[String] list = ...
  update (Updater[String] it : list) {
    if (it.get() == null) {
      it.remove();
    } else {
      it.set(it.get().toUpperCase());
    }
  }

Alternate approach 3 - improve the syntax of the closures proposal

  List[String] list = ...
  removeLoop:update (String str : list) {
    if (str == null) {
      goto removeLoop => true;
    }
    goto removeLoop => false;
  }

The idea here is to provide a mechanism to return the result of the closure clearly and safely. The 'removeLoop' is a label, as currently exists in Java. Here I am using it to clearly identify which closure to return the result to. (Neal Gafter has indicated that yielding a result without identifying the closure to yield to causes issues.) The goto in this proposal also doesn't break Java, as goto is a reserved keyword.

It should be noted that there are many, many possible syntax variations to achieve the effect above. I'm certainly not hung up on this one, although I think it reads pretty clearly, and is safe.

Summary

Well, that outlines where I see a hole in the closures proposal at present, and three or four possible solutions. Next time, I'll try and cover some non-collection use-cases. All feedback welcomed!!

2 comments:

  1. It looks like your filter example will never remove anything. I'd prefer:

    filter(collection,{String item => item==null});

    I.e., I don't think the statement form is appropriate when the stuff that you put in the statement form is really an expression - it returns something instead of modifying something.

    Perhaps this should be formalised in the spec - so that closures in the statement form cannot return stuff. Maybe some other use cases invalidate that, got any?

    ReplyDelete
  2. Stephen Colebourne2 January 2007 18:21

    Ricky, I updated the blog to fix the error in the filter example. However, using the long form for closures doesn't really help either (you need to imagine that this method is way more complex that this example):

    Collection[String] coll = ...
    filter (coll {String str =>
    boolean remove = false;
    if (str == null) {
    remove = true;
    } else {
    // do stuff
    }
    remove
    });

    However you play it, using a last-line-missing-semi-colon to indicate something as fundamental as the result of the closure invocation seems very flawed to me.

    I'll try to post some more use cases tonight. I do find them useful for extracting out issues in the proposal.

    ReplyDelete