An alternative to the fragility of delegation

#1
interface Greeter {
    fun hello(): String
    fun greet() = hello()
}

open class Base {
    open fun Greeter(): Greeter = object : Greeter {
        override fun hello() = "Base hello"
    }
}

class Derived : Base() {
    override fun Greeter(): Greeter = object : Greeter by super.Greeter() {
        override fun hello() = "Derived hello"
    }
}

fun main() {
    println(Derived().Greeter().greet())
}

Surely nobody here would be fooled into thinking that this will print Derived hello. However if you imagine Greeter being defined elsewhere and being part of a large code base, then the Derived class on its own may not raise any red flags. And in general, someone may eventually (or inevitably) write a function that calls another in the same interface, causing subtle breakage.

If the intent was to print “Derived hello”, then one fix would be to replace Greeter with

interface Greeter {
    fun hello(): String
}
fun Greeter.greet() = hello()

Depending on your taste, this may be considered either kludgy or elegant.

Some languages have a more direct way to express the intent of “Derived hello”. That way is to modify the singleton class of an object, or to replace a virtual table entry, or whatever the animal is in the language.

Considering the fragility of delegation, and considering that “Derived hello” is (more often than not) the more useful behavior anyway, I wonder how hard/easy it would be for kotlin to modify a virtual table (or whatever you call them).

I’m not under the illusion that will be taken very seriously, but here is the non-compiling proposed code:

class Greeter {
    abstract fun hello(): String
    fun greet() = hello()
}

open class Base {
    open fun Greeter(): Greeter = object : Greeter() {
        override fun hello() = "Base hello"
    }
}

class Derived : Base() {
    override fun Greeter(): Greeter = object : super.Greeter() {
        override fun hello() = "Derived hello"
    }
}

Some fake generated code – just to show what I mean – is

interface Greeter {
    fun hello(): String = __vtbl.hello(this)
    fun greet(): String = __vtbl.greet(this)

    data class __Vtbl(
        val hello: (Greeter) -> String,
        val greet: (Greeter) -> String
    )

    val __vtbl: __Vtbl get() = Greeter.__vtbl
        
    companion object {
        val __vtbl = __Vtbl(
            hello = { throw AssertionError("abstract call") },
            greet = { greeter -> greeter.hello() }
        )
    }
}

open class Base {
    open fun Greeter(): Greeter = object : Greeter {
        override val __vtbl = Greeter.__vtbl.copy(
            hello = { "Base hello" }
        )
    }
}

class Derived : Base() {
    override fun Greeter() = super.Greeter().let { greeter ->
        object : Greeter {
            override val __vtbl = greeter.__vtbl.copy(
                hello = { "Derived hello" }
            )
        }
    }
}
#2

The problem is in what actually happens here. Even though you call super.Greeter() this is not a constructor invocation, this creates a new object with an anonymous name. This new object is not extended itself, but is used for implementing the Greeter methods, except where overridden. The code is using delegation as if it were inheritance, and it emphatically is not.

In other words, you cannot inherit from/extend anonymous types. You also cannot inherit from object instances (which would be returned from your Greeter function). The concept of delegation is fundamentally different from inheritance and as you noticed works differently (the delegate does not know about being delegated to).

#3

@pdvrieze I’ve double-checked my post and it seems quite apparent that a tutorial on basic concepts is not useful here. I’m all too happy to answer clarifying questions (or any other questions), but you’ve got to ask. Let’s proceed under the assumption that everyone here understands delegation, ok?

#4

_vtbl of Greeter interface cannot be represented as a number of functions taking Greeter as the first parameter. Instead there should be ThisType parameter, where ThisType is the type of the particular interface implementer.

So, if you have AGreeter implementation of Greeter interface, its _vtbl contains (AGreeter) -> String functions, and if you have another one BGreeter, it should have (BGreeter) -> String functions there.

Now if you delegate Greeter interface implementation to AGreeter instance in BGreeter class, i.e. class BGreeter : Greeter by AGreeter(), you cannot just take functions from AGreeter's vtable and copy them to BGreeter's vtable, because they are of different type: they expect to get an AGreeter instance, and after copying they will be provided with a BGreeter instance.

#5

@ilya.gorbunov Well the “fake generated code”, as I called it, is just that. It’s for pedagogy (“just to show what I mean”). Its purpose is to demonstrate how a singleton class works, and what it looks like to override the method of an existing object. JVM restrictions preclude actually changing a method, but we might satisfactorily achieve the effect.

This functionality, found in other languages, is magnificently useful. There’s a song that goes: “you don’t know what you got 'till it’s gone”. There’s a flip side to that – you don’t know what you’ve missed 'till you got it.

I’ve worked on compilers. Now if I start rooting around the kotlin code base and eventually manage to get together a pull request implementing this, is that something that kotlin developers would take seriously? I’m not looking for any assurances, but if it doesn’t align with the vision of kotlin held by developers then it may be dismissed outright.

#6

TLDR: I don’t think it can be implemented, even if it were worthwhile to do so.

If I understand your design what you want to happen is that you want to have an approach of delegation where methods in the delegate that call other delegate methods will use the owning object as instance on which to invoke these methods. (to invoke the method that says delegate)

This function cannot be implemented easily in a way that is compatible with Java. A function in the delegated to object (or better yet any object) does receive a reference to the instance to invoke other methods on. The type of this “this” pointer is that of the object that owns the method. This is needed for example to access private members or members that do not exist in parent types.

Making the “this” parameter a supertype is causing many problems (so you can’t pass the delegating object as this parameter) so the only way to implement anything like it would be a subtype. However a delegate is an instance (of a particular type), not a type. As such subclassing the delegated to object would also not work. The only “valid” subtype would be a proxy type that would do forwarding (but that creates a chicken-egg problem).

You may think there are bigger hammers. Something like invokedynamic, sun.misc.Unsafe or even bytecode rewriting. The latter, when implemented as creating a “derived” rewritten class out of the game immediately as there is no requirement for the delegated to object to be referenced/owned only by the delegating object (it’s bad style, but compilers cannot care about that). The alternative to actually change the original class (and have unrelated users of the delegated to type pay the price) doesn’t work either as there are conflicting requirements as to the type of the this pointer (the delegated to type needs it to be of its own type to access unique members), the delegating type needs it to be of its own type to allow overriding of methods on the delegated member - both types are not compatible (hence proxy objects that “know” where to send requests). Invokedynamic cannot solve the “two incompatible this pointers” problem. I don’t think that unsafe allows you to do the nastiness needed for this solution either.

Is it impossible to create the effect you want on the JVM? No, but you need, as @ilya.gorbunov pointed out, to solve the problem of methods being invoked with different, incompatible, this pointers. Note that while it looks very similar to your proposal, even private (multiple) inheritance as provided by C++ would not work as it works by using a type, not an instance.

The example also complicates stuff by having the Base and Derived classes, where if both Greeter functions where freestanding the entire rest would be identical, including the way it works. In short, in “pseudocode” you would have something like (btw. I’m using named types rather than anonymous types for clarity):

interface Greeter {
    fun hello(this:Greeter, this$1:Greeter): String
    fun greet(this:Greeter, this$1:Greeter) = hello()
}

class BaseGreeter: Greeter {
    override fun hello(this:BaseGreeter, this$1: Greeter) = "baseGreeter"
    // Note that without @JvmDefault the implementation is injected in implementing classes
    fun greet(this:BaseGreeter, this$1: Greeter) = this$1.hello()
}

fun baseGreeter(): Greeter = BaseGreeter()

class DerivedGreeter(base: Greeter): Greeter by base {
    override fun hello(this: DerivedGreeter, this$1: Greeter) = "DerivedHello"
    override fun greet(this: DerivedGreeter, this$1:Greeter) = base.hello(base,this)
}

This solution is not scaleable as it may need a large amount of “this” objects for each derived type. A dispatcher object that is initialised to know the right objects/types to dispatch the right functions to could be used instead. It doesn’t change the fact that this requires the code to be present in not only the concrete type that is delegated to, it also must be present in each interface that could potentially have a delegate implement it (all interfaces).

Even a language like C++ doesn’t really allow what you ask for, private inheritance comes close, but is not the same. As the delegated-to-object is potentially visible outside the delegating-object it is not valid to update the actual type of the object by giving it a different vtable (effectively subclassing).

1 Like
#7

If I’m understanding this correctly, wouldn’t that remove one of the main benefits of delegation, and reintroduce the fragile-base-class problem?