Tuesday 6 February 2007

Java language - method adaption

BGGA Closures has been following one course to address the verbosity of defining an inner class in Java. In this blog I want to consider an alternative that could be more useful in some circumstances.

Method adaption

Consider a long running task that will use the Java 5 executor framework (I could also have used a swing example):

public void init() {
  executor.execute(
    new Runnable() {
      public void run() {
        // lots of code to run in the executor
      }
    }
  );
}

The problem is that frequently, the code to be run gets quite long. So, what may happen is that the code is refactored. One refactoring is to use top-level classes, but lets consider the simpler alternative - method refactoring:

public void init() {
  executor.execute(
    new Runnable() {
      public void run() {
        processTask();
      }
    }
  );
}
private void processTask() {
  // lots of code to run in the executor
}

However, the developer is really having to do a lot of tedious drudgery to achieve the goal here. Could it be simplified? Perhaps we could use method literals:

public void init() {
  executor.execute(this->processTask());
}
private void processTask() {
  // lots of code to run in the executor
}

So, two things have happened here. Firstly, a method literal has been used - this->processTask(). This represents the processTask() method on this specific object.

The second thing that has happened is that an adaptor has been generated for us. This is a new class that implements Runnable, and calls processTask() from the run() method exactly as we would have if we coded it ourselves.

Obviously, there is a restriction that the method signature of processTask() must match the method signature of Runnable.run(). But that seems perfectly reasonable.

My one reservation is whether too much information has been lost with this. (The choice of Runnable is being inferred in the example.) One variation is to explicitly specify the API and method that is to be adapted to:

public void init() {
  executor.execute((Runnable->run()) this->processTask());
}
private void processTask() {
  // lots of code to run in the executor
}

Here in this variation, the 'cast' is specifying the method signature to be adapted to, again using a method literal (this time not tied to a specific instance). This is a more general syntax, as it also allows assignment to a variable:

public void init() {
  Runnable r = (Runnable->run()) this->processTask();
  executor.execute(r);
}
private void processTask() {
  // lots of code to run in the executor
}

The advantage of this variation is that it handles cases like MouseListener where there are many possible methods to adapt to. This disadvantage is that there is more code which could be inferred.

But which of the two variations works best? Or perhaps the first is just a special case of the second? And is a 'cast' the appropriate syntax here?

Summary

Method adaption could be a useful language change. It certainly doesn't compete with BGGA closures for raw power, but it does provide an alternative for many async use cases that could be viewed as being a lot clearer.

As always, please view this as a concept for discussion (and its certainly not a new idea) I've not proven it can be implemented, but there's nothing obvious hitting me yet. As such, all opinions are welcome...

7 comments:

  1. How would the object instance and paramters interact with this? For example, would this be legal:
    Runnable->run r = foo->someMethod(bar, baz);

    ReplyDelete
  2. Stephen Colebourne6 February 2007 at 08:56

    @Jesse, Your example would not be legal - the adaption must be to a method with the same signature.

    @m, I offered two options - one with the cast and one without. For executor, its obvious what to do, so perhaps the without a cast version is an optimised form of the with a cast version.

    For method literals, I'm choosing to make 'this' mandatory, so your example won't come about.

    As I said, this is an alternative to closures. If you've read my other posts, I object to using closures when there are two sets of rules for what you can and can't write in the body of the closure. Method adaption is one possible solution to some closure use cases.

    ReplyDelete
  3. "I object to using closures when there are two sets of rules for what you can and can't write in the body of the closure. Method adaption is one possible solution to some closure use cases."

    But your proposal only addresses a tiny part of all the possible use-cases that closures would open up. So this would either mean forgetting about all the other things that closures would bring to the table (a wasted opportunity IMO) or find alternatives for all/most of them (which seems silly).

    "@Jesse, Your example would not be legal - the adaption must be to a method with the same signature."

    But what if I have my own Runnable that does have a method like that? Something like this for example:

    MyRunnable r = (MyRunnable->run(int,int))this->processTask(foo,bar);

    If that's possible in which context would foo and bar be resolved? And how would it be possible to pass arguments to the constructor of MyRunnable?

    Or is your proposal only meant to be used with Runnable?

    ReplyDelete
  4. What about using labels for the cases where the adapted interface has more then one method?
    For example:

    window.addKeyListener(
    keyPressed: this->keyDown(KeyEvent),
    keyReleased: this->keyUp(KeyEvent)
    );

    This wound be translated to:

    window.addKeyListener(new KeyListener(){
    public void keyPressed(KeyEvent e) { keyDown(e) }
    public void keyReleased(KeyEvent e) { keyUp(e) }
    public void keyTyped(KeyEvent e) { }
    });

    Each label indicates which method has to be called. Any method not labeled is implemented as "do nothing". Alternatively, the programmer should be obligated to label all methods, with some special syntax to indicate the "do noting" intend.

    ReplyDelete
  5. Stephen Colebourne6 February 2007 at 15:22

    @Quintesse, I know this only addresses some of the closure use cases. Thats why I used the word 'some' in the blog :-)

    Your code fragment is not what I intend, this is what I'm thinking of:

    MyRunnable r = (MyRunnable->run(int,int))this->processTask(int,int);

    Your foo and bar have no place here. The two int parameters are just passed through within the generated inner class.

    It would not be possible to pass parameters to the constructor of the inner class.

    @Carlos, Labels is an interesting idea. Not sure if it overcomplicates though. Why not just add two listeners:

    window.addKeyListener((KeyListener->keyPressed) this->keyDown(KeyEvent));
    window.addKeyListener((KeyListener->keyReleased) this->keyUp(KeyEvent));

    ReplyDelete
  6. I think anonymous code blocks (a la BGGA) are easier to read and better fit good code style rather than trying to make arbitrary methods on my class that happen to look like methods in another class. And if I need to map multiple methods, I think current mechanisms in Java are good enough.

    I still think method references might be helpful to avoid reflection in some cases, though, and it would be nice if property reference syntax also worked for methods. But again, that's mostly just a reflection avoidance issue. Probably not a common programming case. Well for properties yes, but not for methods.

    ReplyDelete
  7. Or in other words, I think BGGA is easier to read, more useful, and is more likely to lead to good code.

    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.