Super/Inner

The title of my last post was “Saturn?”, which I never really explained. Obviously, Saturn is an alternative name for the new implementation of neptune. The reason why I’ve considered changing the name is both that the language I’m implementing now is somewhat different from neptune and to avoid potential, how shall I put it, “conflict” with Esmertec (not that I expect any problems with Esmertec but just to be on the safe side).

Last week I hadn’t made up my mind about it but now I have: I’m changing the name. Neptune is dead; long live Saturn!

The final straw was the decision I made today to support both super and inner calls. Super calls are a well-known mechanism that exist in most object-oriented languages. Inner calls, a mechanism that originally comes from the beta language, are much less known and completely unknown in mainstream languages.

In most object-oriented languages, overriding a method means replacing it completely. An overriding method can call the overridden method through a super call, but it is entirely up to the implementer of the overriding method if and how it wants to do that.

class Window {
paint() {
...; // paint background
}
}

class BorderWindow: Window {
paint() {
super.paint(); // paint background
...; // draw border
}
}

This works well most of the time since it gives overriding method complete control while it still has the option of calling the overridden method. Occasionally, though, having the subclass in the driver’s seat just isn’t what you want. For instance, sometimes there is no good place to call super in the subclass. One place I’ve met this is when implementing caching in a superclass: the superclass handles caching and subclasses are only called upon to calculate a value if there is a cache miss.

abstract class Node {

get_index() {
if (has_cached_index(this))
return get_cached_index(this);
int index = calculate_index();
cache_index(this, index);
return index;
}

abstract calculate_index();

}

class SomeNode: Node {

override calculate_index() {
...; // expensive calculation
}

}

Here, the superclass handles caching in get_index and the subclasses can specialize how the index is calculated by overriding calculate_index. But this approach is not without problems: it introduces two methods that are deceivingly similar but must be used in two different ways. Only get_index is allowed to call calculate_index directly, since otherwise you won’t get the proper caching behavior. On the other hand, subclasses must override calculate_index, not get_index, but must of course not call calculate_index themselves.

This is where inner comes in. An inner method, as opposed to an overriding method, specializes a method in a superclass but doesn’t replace it. Instead, the method in the superclass can explicitly call the inner method in the subclass:

class A {

print() {
System.out.println("a");
inner.print();
}

}

class B: A {

inner print() {
System.out.println("b");
}

}

new A().print();
> a

new B().print();
> a
> b

Here there are two print methods but when you call print on an instance of B, the method in A will be called, not the one in B. It will only be called when the super implementation calls inner.print(). It’s in some sense overriding turned upside down, with calls downwards in the inheritance hierarchy through inner instead of calls upwards through super. Using this, we can rewrite the caching example from before:

abstract class Node {

get_index() {
if (has_cached_index(this))
return get_cached_index(this);
int index = inner.get_index();
cache_index(this, index);
return index;
}

}

class SomeNode: Node {

inner get_index() {
...; // expensive calculation
}

}

Now there is only one method so there is no confusion about which method to call and which method to override.

As demonstrated above, you don’t need inner as such since you can always just create a new method like calculate_index instead. However, a lot of situations where you would usually use super can be described more elegantly using inner. For instance, if you’ve ever come across a method that required overriding methods to call the super method first (that happens often in eclipse), that’s really just an inner call struggling to get out:

class AbstractPlugin {

/**
* Overriding methods must call super first.
**/
start() {
...; // start plugin
}

}

class MyPlugin: AbstractPlugin {

start() {
super.start();
...; // start my plugin
}

}

With inner methods that could be rewritten as:

class AbstractPlugin {

start() {
...; // start plugin
inner.start();
}

}

class MyPlugin: AbstractPlugin {

inner start() {
...; // start my plugin
}

}

Here, subclasses don’t have to implement start in any particular way, and the fact that the superclass implementation must be executed first is encapsulated in the super implementation of start. Also, the superclass is free to call the inner method however it wants: conditionally, three times, not at all, etc., which is impossible with super calls.

There’s a bit more to inner calls, for instance: what if there is no subclass implementation, how do inner calls interact with super calls, etc. All that is described in the paper I referenced above, Super and Inner – Together at Last!. The model in Saturn is pretty close to the one in the paper (but not exactly the same).

2 Responses to Super/Inner

Leave a Reply

Your email address will not be published. Required fields are marked *


*