Tuesday, 29 August 2006

Closures - async bad, sync good

Closures. Great or not? Well given its properly up for discussion I thought I'd add my opinions. (I'll assume you've read the various blogs - Neal's most recent two are perhaps the most helpful).

Two separate use cases have been identified for consideration - async and sync. Async is where code is to be executed later, such as timer tasks. Sync is where the code is executed immediately by the current thread. The immediate question is "why is there is a need for one language syntax to meet both requirements?".

Async use case

The async case is already dealt with in Java - with inner classes (typically anonymous). With inner classes, the timer task asks us to pass in 'what we want doing'. So we pass in an object that represents the 'task'. Its logically more than just a block of code.

Now some may complain that its too much typing or too much clutter. But we already have it. The need is met. Now lets look at some issues with the async closures.

a) The syntax of an async closure will be subtly different to that of a sync closure. Once written however, you won't be able to tell which is which, until you try and edit it. This fails an important language syntax readabiity test. In effect, the developer is being lied to, as they are writing a method with limited control flow, but it looks no different to a code block from the sync case.

b) Refactoring an anonymous inner class to a closure converted inner class is not staightforward. (Neal uses the test of how easy it is to refactor code from a method into the closure - this is the opposite test). This point is really about which refactoring should be easy? Moving code to execute in another thread is a serious refactoring, with potentially serious impact. Surely, its not unreasonable for that to require brainpower?

c) An async closure will produce weird error messages - 'nonlocal return/break/continue'. What do these mean, or rather how can they be explained. Lets use this test - write an error message for the compiler in 12 words that a junior developer will understand?

d) May discourage re-use of 'task' instances, which as real objects should often be designed and re-used. Developers will simply be less aware that they are passing an object around (as it doesn't look like an object). Thus there may well be more inlined code.

e) Multi-method adaptor classes like those in Swing don't play well. In fact, you'll have to write an inner class...

Point (a) is really important. Thus, its no surprise that at present, I am very much on the side of inner classes for the async use case and against closures.

Sync use case

The sync use case is entirely different. Here, the whole point of the use case is that the code is executed inline within the method. Thus, it really should be pretty indistinguishable from any other block of code. And it has the potential to add real value to the language.

I like to think of this as being implemented in a very simple way. The compiler could take the first half of the closure implementation and insert it before the closure code block, and the second half after the code block. Very simple, the only downside being bloat of the client code. For example, considering:

public void read() {
  closeAfter() (InputStream in) {
    in = openStream();
    if (in == null) return;
    readStream(in);
  }
}

public static void closeAfter(void(InputStream) closure) {
  InputStream in = null;
  try {
    closure(in);
  } finally {
    in.close();
  }
}

The compiler would produce:

public void read() {
  InputStream in = null;
  try {
    in = openStream();
    if (in == null) return;
    readStream(in);
  } finally {
    in.close();
  }
}

Now, I'm no language implementation expert, but this looks very simple to do, exceptions and control flow just happen naturally. In particular, data assigned to the variable 'in' is available for use at the end of the closure implementation.

Now, I just need to provide a nice syntax for returning a value from the closure. Maybe assigning it to a parameter like we did with 'in'?

So, yes, I probably am in favour of sync closures (combined with extension methods. Just so long as the syntax and implementation is simple and without external artifacts.

Of course it could be I've misunderstood something, but really the two use cases seem poles apart to me. Closures fit with the sync case, but not with the async one (in Java - in other languages it is probably very different). Trying to make one 'syntax' fit both is misleading and dangerous, as the complete syntax must also take into account errors and unusual conditions and be fully consisent across both.

No comments:

Post a Comment