genericskotlingeneric-variance

How to implement a Kotlin interface that refers to the conforming type?


In the interest of silly thought experiments whose primary purpose is to explore how part of a language works, I decided I wanted to explore a method of making Python programmers more comfortable in Kotlin. Simplistically, I can do this by adding:

class Foo {
    private val self:Foo get() = this
    ...
}

(Aside Question: Is there a more generalized way to refer to Foo as the return type there so that if I changed Foo to Bar, the variable type of self would still refer to the "implementing class of this method"?)

Having to put that line in each class so that we can feel selfishly pythonic is tedious though. So I turned to an Interface. What I initially wanted is something like Swift's Self type for Protocols. But I couldn't find anything like that in Kotlin. After reading https://kotlinlang.org/docs/reference/generics.html (which seems to be about Java as much as Kotlin), I concluded that perhaps "Declaration Site Variance" was the thing for me:

interface Selfish<out T> {
    val self:T get() = this as T
}

class Foo:Selfish<Foo> {
}

This is better. It's undesirable that I have to list the class name twice in the declaration, but I don't think there's a way around that. Is there?

Additionally, this works for final classes, but if I want to have a class hierarchy that conforms to Selfish at the root level, things fall apart:

class Foo:Selfish<Foo> { ... }
class Bar:Foo { ... }

Methods in Bar that use self are of the wrong type. And adding , Selfish<Bar> creates a conflict.

Is there a tool I haven't discovered yet to make the type refer to inherited type?

Is there another approach (other than Interfaces) to do something like this?

Did I make the wrong choice using "Declaration Site Variance"?


Solution

  • I think you should take a look at extensions.

    So you can write

    fun <T>Foo.getSelf(): T {
        return this as T
    }
    

    Then if you have

    open class Foo
    
    class Bar: Foo()
    

    so

    Bar().getSelf<Bar>()
    

    will return object of Bar class

    Or even easier, you can write

    fun <T:Foo>T.getSelf(): T {
        return this as T
    }
    

    so you can call just

    Bar().getSelf()
    

    to get instance of any class extended from Foo