Search code examples
dynamicdispatchkotlin

What are the limits on dynamic/double dispatch in Kotlin?


I'm just starting to explore Kotlin, and I'm curious about how far it moves beyond Java's core dynamic binding/dispatch semantics.

Let's say I write code that looks something like this:

class Animal {
    fun add(x:Animal) = Animal()
}

object Horse : Animal
object Donkey : Animal
object Mule : Animal

fun Horse.add(x: Horse) = Horse()
fun Horse.add(x: Donkey) = Mule()

fun main(args : Array<String>) {
    val h: Animal = Horse
    val d: Animal = Donkey
    val child = h + d
}

Based on the above code above, what can I expect to happen? Do I get a failure at runtime because Horse doesn't implement add(Animal)? Can it accurately differentiate them in calls of the above nature, where the compile-time type of the values being compared was Animal (at least, as written) but their runtime types were more specific? Does it change anything if we used var instead of val?

Thanks in advance.

EDIT: Modified core code--I see the problem the first responder highlighted, I wasn't thinking straight. Clearly I haven't actually compiled this, I'm still kind of exploring at a conceptual level.

Also, I will give it a shot in the actual compiler, but I'm concerned that there will be situations in which it works and others in which it doesn't based on some criteria that I don't fully understand. I wasn't able to find reference docs on how dynamic dispatch is implemented in Kotlin (not sure about it for Java either, for that matter; I wrote something a few months ago that I thought would work based on JVM docs, but it didn't, and I never had a chance to explore exactly why).

Anyhow, thanks again!


Solution

  • So here's a version of your code that actually compiles:

    fun main(vararg args: String) {
        val h:Animal = Horse
        val d:Animal = Donkey
        val child = h + d
        println(child)
    }
    
    open class Animal {
        fun plus(x:Animal) = Animal()
    }
    
    object Horse : Animal()
    object Donkey : Animal()
    object Mule : Animal()
    
    fun Horse.plus(x:Horse) = Horse
    fun Horse.plus(x:Donkey) = Mule
    

    The result is "Animal@1906bcf8".

    As far as I understand, extension methods, i.e. Horse.plus(x:Horse) and Horse.plus(x:Donkey), are statically dispatched. That's because they are basicly compiled to the same byte code as the following Java code:

    static Horse plus(Horse $receiver, Horse x) {
        return Horse.INSTANCE;
    }
    

    By the way, this is a big difference to default methods in Java 8 which are dynamically dispatched based on the runtime type and can be overriden.