Thursday, March 18, 2010

Multiple dispatch (and poor men's patter matching) in Java

There are times where you do not want to or simply cannot add a certain aspect of behavior to your domain model's class hierarchy.
Most applications are layered but share the same domain model (the times where people translate state into layer-specific representations are hopefully gone). Sharing the same domain model classes between different layers is convenient and effective. However if you do that, your domain model tends to become a so called anemic domain model, that is you do not add the behavior to your classes but hold it externally in order to keep the domain model layer independent. This ensures that your domain model only contains code which belongs to the domain and not to a technical layer. That's ok because you do not want to have UI-specific or database-specific code in your domain model if you pass it through all layers.

The problem is that you cannot leverage Java's polymorphism anymore. As a result you either write lengthy if-else cascades in order to dispatch between different types or you use the visitor pattern, which is some kind of external polymorphism support. Well actually the old visitor pattern is not that much external but very invasive. You have to prepare your domain model to be able to accept visitors. That's not always possible and also a lot of work.

Other languages support features to solve this problem. One is pattern matching as known from functional languages which also has made its way to object oriented languages. It's nice and powerful but not available in Java. :-(
Another feature is "multiple dispatch". Languages supporting multiple dispatch (aka "multimethod") decide which method to invoke based on the actual runtime type of the passed arguments. Java in contrast links the method at compile time based on the static types.

This is why in Java the following would print "Hello Fruit":
print((Fruit) new Banana());

static void print(Fruit f) {
sysout("Hello Fruit");
}

static void print(Banana b) {
sysout("Hello Banana");
}
If Java would support multiple dispatch it would print "Hello Banana", because at runtime the passed argument is an instance of Banana.

In Xtext we have "added" multiple dispatch to Java (we call it polymorphic dispatch). For instance a label provider is implemented like this:

public class MyLabelProvider extends AbstractDeclarativeLabelProvider {

String text(Banana b) {
return "yellow fruit";
}

String text(Apple b) {
return "keeps the doctor away";
}
}
The framework just calls ILabelProvider.getText(Object element) and the implementation of this method dispatches to the best matching label(Object) method. So if you are interested how it works, have a look at the PolymorphicDispatcher class. Other examples where we use this technique are validation, scoping, qualified name provider, content assist, outline view, etc.

This stuff is very useful for defining such kinds of APIs because it allows us to define nice defaults while the user can add specific behavior just by adding the corresponding methods.

But what if you are in the middle of some implementation and only need to make some decisions based on the type? Write code like this?

Fruit fruit = new Banana();
String result=null;

if (fruit instanceof Banana) {
Banana b = (Banana) fruit;
result = // do stuff with b;

} else if (fruit instanceof Apple) {
Apple a = (Apple) fruit;
result = // do stuff with a;

} else if (fruit instanceof Peach) {
Peach p = (Peach) fruit;
result = // do stuff with p;
}

System.out.println(result);

You might get into trouble with these guys.
How about this one?:

System.out.println(
new Match() {

String is(Banana b) {
return // do stuff with b;
}

String is(Apple a) {
return // do stuff with a;
}

String is(Peach p) {
return // do stuff with p;
}

}.apply(new Banana()));

It's only a bit shorter, but I like it much better because it is functional and more declarative. That is I don't have to declare variables and do side-effects within the different if statements. Also this works for multiple parameters as well.

P.S.: If your domain model is implemented in EMF you get a so called Switch-class generated, which acts similar. So if you only dispatch within that hierarchy and you only need one argument, this works great as well.

5 comments:

  1. Carsten Pfeiffer3/18/10, 12:33 PM

    Such dispatching is also a neat feature in Object Teams. See this simple example where a FruitLabelProvider has a role for every fruit, implementing getLabel(). Sorry for the poor formatting, but <pre> is not allowed.

    FruitLabelProvider.getLabel(Fruit as FruitLabel someFruitLabel) does the actual work of looking up the correct role that matches the given Fruit instance, although it is unknown at compile time.

    public team class FruitLabelProvider {
    public String getLabel(Fruit as FruitLabel someFruitLabel) {
    return someFruitLabel.getLabel();
    }

    protected class FruitLabel playedBy Fruit {
    public String getLabel() { return "Unknown exotic fruit"; }
    }

    protected class BananaLabel extends FruitLabel playedBy Banana {
    public String getLabel() { return "Banana"; }
    }

    protected class MangoLabel extends FruitLabel playedBy Mango {
    public String getLabel() { return "Mango"; }
    }
    }

    Usage would simply be something like this:

    Fruit b = new Banana();
    Fruit m = new Mango();
    Fruit f = new Fruit() {};

    FruitLabelProvider label = new FruitLabelProvider();
    System.out.println(label.getLabel(b));
    System.out.println(label.getLabel(m));
    System.out.println(label.getLabel(f));

    PS: loved the link to the anti if campaign :-)

    ReplyDelete
  2. So what is your postion on the "anemic model" antipattern? Are you saying it is OK if you have multiple dispatch?

    ReplyDelete
  3. @Adam: I usually put abstractions which strongly bekong to the domain model (like derived features and the like) into the domain model. But there's often a lot of technical, layer-specific code which you do not want to put into the domain model. Therefore people use static helper classes, visitors, etc. I prefer to use polymorphic dispatching in this case also I prefer to obtain theses additional functionality through the means of dependency injection. This ensures that I can exchange the implementation easily.

    ReplyDelete
  4. Hi, I don't understand where is this Match class from.
    Is it from Xtext, Eclipse or other?

    ReplyDelete
  5. No it's not checked in somewhere but looks basically like this:

    class Match {
    private PolymorphicDispatcher dispatcher = PolymorphicDispatcher.createForSingleTarget("is",this);

    public Object apply(Object o) {
    return dispatcher.invoke(o);
    }
    }

    ReplyDelete