Introduction Example Documentation Download Links Contact

Introduction

The Runabout is an extension of the Java libraries that adds two-argument multi-dispatch to Java without changing the language or the VM. Like the Walkabout, the Runabout uses reflection to find visit methods. But instead of invoking the visit methods with reflection, the Runabout uses dynamic code generation to create code at run-time that will invoke the appropriate visit method. This puts the Runabout closer to MultiJava, a Java source compiler that compiles Java with multi-methods to ordinary Java bytecode. Unlike MultiJava, the Runabout runs when the application is executed, and not at compile time. Writing code with the runabout is very similar to writing visitors or multi methods with MultiJava.

The dispatch in the runabout is a factor of 3 slower than ordinary uni- or double dispatch (on Sun's JDK 1.4.1 for 10 million invocations). Detailed performance numbers are in my ECOOP 2003 paper.

Example

import org.grothoff.Runabout;

public class Example {
    public static void main(String[] args) {
    	MyRunabout mr = new MyRunabout();
	Object[] objects 
	    = new Object[] { "Hello World",
			     new A(),
			     new B() };
	for (int i=0;i < objects.length;i++)
	    mr.visitAppropriate(objects[i]);
	if (mr.counter != 7)
	    throw new Error("This does not happen.");
    }
    public static class MyRunabout extends Runabout {
	int counter = 0;
	public void visit(String s) {
	    if (s == "Hello World")
		counter = 1;
	}
	public void visit(A a) {
	    counter+=2;
	}
	public void visit(I i) {
	    counter+=4;
	}
    }
    public static class A {}
    public interface I {}
    public static class B implements I {}
}
    

Documentation

Using the Runabout is quite straightforward. You download the JAR-file and add it to the classpath of your Java application. Then, you create public classes that extend Runabout. In these classes, you place visit methods that return void and take one argument which must also be of a public type. The Runabout then allows you to invoke the visit method that is the best match for a given object using visitAppropriate. The semantics for best match are equivalent to overloading of methods in Java, only that the Runabout uses the dynamic type, where overloading uses the static type.

Examples of Java code "asking" for the Runabout

A common example for Java code that asks for refactoring with a Runabout looks like this:

  class Example2 {
    void run(Object a) {
      if (a instanceof A)
        foo((A)a);
      else if (a instanceof B)
        bar((B)b);
      else if (a instanceof C)
        ...
    }
  }
    

Sometimes, a visitor can be a good solution here, but if for example A is String or any other legacy code that can not be given an accept method, this is not possible. With the Runabout, the code can be cleaned up with a result like this:

  public class Example2 extends Runabout {
    public void visit(A a) { foo(a); }
    public void visit(B b) { bar(b); }
    public void visit(C c) { ... }
    // ...
 
    void run(Object x) {
        this.visitAppropriate(x); 
    }
  }
    

Another case is are visitors that are getting too complex...

  interface I { 
    void accept(MyVisitor m); 
  }
  static class A implements I { 
    void accept(MyVisitor m) { m.visit(this); } 
  }
  static class B extends A {
    void accept(MyVisitor m) { m.visit(this); } 
  }
  static class C {
    void accept(MyVisitor m) { m.visit(this); } 
  }
  interface MyVisitor {
    visit(I i);
    visit(A i);
    visit(B i);
    visit(C i);
    // ...
  }
  class MyIABVisitor implements MyVisitor {
    void visit(I i) {
      // ...
    }
    void visit(A a) {
      visit((I)a);
    }   
    void visit(B b) {
      visit((A)b);
    }
    void visit(C c) {
      throw new Error("IAB Visitor only works for I, A and B");
    }
    // ...
  }
  class MyIBCVisitor implements MyVisitor {
    void visit(I i) {
      // ...
    }
    void visit(A a) {
      throw new Error("IBC Visitor only works for I, B and C");
    }   
    void visit(B b) {
      // ...
    }
    void visit(C c) {
      // ok here
    }
    // ...
  }
    

This example shows two issues that can make visitors compilicated. irst, if the set of visitee types (here I, A, B and C) forms some type of hierarchy, visit calls that merely indirect to other visit methods can become common. If the hierarchy changes frequently and/or is huge, writing and maintaining these simple indirection calls can be troublesome. Also, if the visitee types often is only partially applicable, say some visitors operate on the subset {I, A, B} and others operate only on {I, B, C}, visitors can have the problem that either an accept method and a specific base-type is required for every set or that an excepting visit method must be placed in every visitor that does not expect to visit that specific type.
In both cases, the Runabout avoids these problems. Client code only needs to define the visit methods that are used and indirections to other visit methods also do not need to be specified in most cases.

  public interface I {}
  public static class A implements I {}
  public static class B extends A {}
  public static class C {}
  public class MyIABVisitor extends Runabout {
    public void visit(I i) { // covers all subtypes of I!
      // ...
    }
    public void visitDefault(Object o) {
      throw new Error("IAB Visitor only works for I, A and B");
    }
  }
  public class MyIBCVisitor extends Runabout {
    public void visit(I i) {
      // ...
    }
    public void visitDefault(Object o) {
      throw new Error("IBC Visitor only works for I, B and C");
    }   
    public void visit(B b) {
      // ...
    }
    public void visit(C c) {
      // ok here
    }
    // ...
  }
    

Note that the visitDefault methods are not even necessary in this example (unless a custom error-message is desired) since the Runabout would throw an Exception by default anyway.

Contraindications

The Runabout requires that all visitee types are public classes or interfaces. Also all subclasses of Runabout must be public, thus anonymous inner classes can not be Runabouts since they are not public (note that this is in practice not a problem since anonymous inner classes can always be changed into named inner classes at no extra cost).
The Runabout requires dynamic class loading and reflection, thus code using the Runabout will not work on Java Virtual Machines where the environment (e.g. the security manager) does not allow dynamic class loading.

Additional Information

Download

The Runabout is dual-licensed under both the GNU Public License (GPL) and the Eclipse Public License (EPL). You can download the source code here:
runabout-1.4.0.tar.bz2
The 1.x distribution contains the sources, testcases, and benchmarking code. The Runabout is in the ovm.util.Runabout package.
runabout-2.1.0.tar.bz2
The 2.x distribution contains just the binaries for the Runabout and the Runabout Optimizer as well as the JavaDoc for the Runabout. The Runabout was moved to org.ovmj.util.Runabout.
runabout-5.0.tar.bz2
The 5.x distribution contains the binaries and source for the Runabout as well as the JavaDoc for the Runabout. The Runabout was moved to org.grothoff.Runabout. The 5.x distribution works only with Java 5.0. The 5.0 release takes the Runabout code to a new extreme in terms of simplicity. Essentially, only the features that were actually used in the XTC framework were kept. The goal is to make it easier for users to understand code using the Runabout by avoiding feature-bloat.
The major changes are elimination of features, namely there is no longer support for primitive types, no longer support for user-defined dispatching functions and no Runabout Optimizer. Primitive types interact badly with auto (un)boxing in Java 5.0 and the other features were so rarely (never?) used that it seems to be better to not have them (keep it simple). The complete sources for the 5.x version are available as part of the XTC framework.
Users that truely need complex dispatching solutions (such as user-defined dispatching or true multiple-dispatch on more than just the first argument) should consider other variations of the Runabout. For example, the sprintabout and PolyD are free, alternative complex general frameworks that were motivated by the Runabout and maybe good solutions for users with stronger needs in terms of expressiveness. The Runabout on the other hand is targeting users that want to keep it simple.
Runabout.java (SVN)
This is the latest development version, directly from the Subversion repository.

Links

Contact

The Runabout was written by Christian Grothoff.
christian@grothoff.org