About Me

My photo
Author of Groovy modules: GBench, GProf, Co-author of a Java book パーフェクトJava, Game Developer at GREE

Saturday, December 22, 2012

Multiple Dispatch in Modern JVM Languages

What is dispatch?

"dispatch" in the context of object-oriented languages means that calling one of subroutines that have the same names by checking the parameters dynamically. There are multiple and single for dispatch. I'll write about dispatch and its support in Java and other modern JVM languages in this post.

Java

Java is "single dispatch". I'll describe it with an example of handling touch events. Let me assume that there are classes that express touch events and they have parent class named TouchEvent, also, there is TouchEventHandler that handles touch events (the y might have a virtual upper layer in an actual API).

class TouchEvent {}
 
class TouchStartEvent extends TouchEvent {}
class TouchMoveEvent extends TouchEvent {}
class TouchEndEvent extends TouchEvent {}
class TouchCancelEvent extends TouchEvent {}
 
class TouchEventHandler {
    void handle(TouchEvent e) {}
} 

Let me assume that you want to create a handler that changes behaviour depending on if the the parameter is TouchEvent or TouchStartEvent. I guess that you will write code like this if you expect subtype polymorphism.

class MyTouchEventHandler extends TouchEventHandler {
    public void handle(TouchEvent e) {
        System.out.println("... ?:(");
    }
    public void handle(TouchStartEvent e) {
        System.out.println("touch started :)");
    }
}

But the code doesn't work well.

TouchEventHandler h = new MyTouchEventHandler();
h.handle(new TouchStartEvent()); // prints "... ?:("

Java refers MyTouchEventHandler but it calls handle(TouchEvent) but not handle(TouchStartEvent). In more technical terms, Java resolves the type of the receiver dynamically but resolves the other parameters statically when sending the message. This is called "single dispatch". I'll modify the code to enhance the gap between expect for the code and compiler's opinion.

class MyTouchEventHandler extends TouchEventHandler {
    @Override public void handle(TouchEvent e) {
        System.out.println("... :(");
    }
    @Override public void handle(TouchStartEvent e) {
        System.out.println("It's my turn! :)");
    }
}

Then compiler will say

src/Overloading.java:19: error: method does not override or implement a method from a supertype
    @Override public void handle(TouchStartEvent e) {
    ^

There is a way to simulate "multiple dispatch" in Java code (without tricks in compilation phase) and I'll describe it in the following section about Xtend.

Xtend

Xtend is a relatively new JVM language that has appeared in the last year ("relatively" means that a new JVM language has appeared in this year too). Xtend is "single dispatch" as Java, but it also supports "multiple dispatch" by "dispatch" keyword.

class MyTouchEventHandler extends TouchEventHandler {
    def dispatch void handle(TouchEvent e) {
        println("... :(")
    }
    def dispatch void handle(TouchStartEvent e) {
        println("touch started :)")
    }
}

val TouchEventHandler h = new MyTouchEventHandler()
h.handle(new TouchStartEvent()) // prints "touch started :)"

It's easy to grasp what Xtend does because Xtend generates Java files instead of class files. Xtends puts a proxy method that delegates to methods have "dispatch" keyword. This is the way to simulate "multiple dispatch" in Java that I mentioned before.

public class MyTouchEventHandler extends TouchEventHandler {
  protected void _handle(final TouchEvent e) {
    InputOutput.<String>println("... :(");
  }
 
  protected void _handle(final TouchStartEvent e) {
    InputOutput.<String>println("touch started :)");
  }
 
  public void handle(final TouchEvent e) {
    if (e instanceof TouchStartEvent) {
      _handle((TouchStartEvent)e);
      return;
    } else if (e != null) {
      _handle(e);
      return;
    } else {
      throw new IllegalArgumentException("Unhandled parameter types: " +
        Arrays.<Object>asList(e).toString());
    }
  }
}

I think that using one underscore for prefix isn't nice because programmers need to worry about naming collision but let's leave it aside for now. My interest is why Xtend is "single dispatch" by default. I asked the question to Sven Efftinge who is one of the authors of Xtend and he answered "because changing method resolution would impose many interop issues with existing Jave libs". I guess it's unhappy that spoiling writability of new lauguage for taking care about bad old Java code depend on "single dispatch" but it might be because that I have never had an awful experience. Anyway, Xtend supports "multiple dispatch".

Groovy

Groovy is "multiple dispatch". The example for Java works perfectly in Groovy as expected. It is so intuitive.

TouchEventHandler h = new MyTouchEventHandler();
h.handle(new TouchStartEvent()); // prints "touch started :)"

The type of the parameters are detected dynamically and handle(TouchStartEvent) is called. I'll explain more details. Groovy replaces the method call with a CallSite object that has the caller class, the index for the call, and the method name. It is like this by expressing in code.

TouchEventHandler h = new MyTouchEventHandler();
CallSite handleN = // create a call site for the "n:handle"
handleN.call(h, new TouchStartEvent());

CallSite searches the target method gradually by listing up the meta data of "handle" methods of MyTouchEventHandler and checking the number and the type of the parameters. Of course this dynamic resolution comes at a cost but CallSite caches the method and will call the method immediately at the second time. Recent Groovy has @StaticCompile that is an annotation for turning off dynamic capabilities. Where use the annotation is compiled to binary that doesn't use CallSites and calls methods statically. In that case "multiple dispatch" doesn't work.

Others

I checked a bit about "multiple dispatch" support of other JVM languages. It is seemed that Clojure has "defmulti" macro corresponds to "dispatch" keyword of Xtend. Scala doesn't support but they might say that it's unnecessary because of powerful pattern matching. JRuby and Jython don't support because Ruby and Python don't support it. I'm wondering also about Kotlin but I'll end this post here because I'm full :-P