How about "this" as a type?

92 Views Asked by At

I will simplify the problem that got me to this point, but I was thinking about this block of code :

abstract class A {
    fun myCustomApply(block: A.() -> Unit): A {
        this.block()  // block() would have the same effect
        // Do something else here
        return this
    }
}

class B : A() {
    fun anotherFunction() {
        // Do something
    }
}

This code looks (and is) ok. But consider the following snippet :

fun doSomethingVeryUseful(): B /* Won't work */ {
    val b = B()
    return b.myCustomApply {
        anotherFunction()  // Nope
    }
}

Here, the compiler will complain, for two reasons, that may not seem obvious at first glance. First of all, myCustomApply's block parameter was declared with type A.() -> Unit, so we can't call anotherFunction() within its scope, because it was declared in class B. Then, it appears that doSomethingVeryUseful's return type was declared as B, but myCustomApply returns A, so we have to use an as A cast.

Both errors are logical, because the compiler can't guess by itself "Oh yeah, the return type is A but it always returns this, so it is perfectly fine to do that !", but these are kinda painful to handle properly because I personnally can't think of a proper workaround.

Of course, you could do something like this :

abstract class A

class B : A() {
    fun anotherFunction() {
        // Do something
    }
}

fun <T : A> T.myCustomApply(block: T.() -> Unit): T {
    this.block()
    // Do something else
    return this
}

But I personnally think this is not a good idea, because it confuses the code (where does the myCustomApply come from ? Why isn't it in class A ?), and is a bit complicated for such a simple problem. You're free to think this is a good solution, but I don't really agree.

My idea was to do something like this (don't try this in your IDE, it will just complain that you're the worst developper ever and that you doesn't even deserve to exist because it isn't actual syntax) :

abstract class A {
    fun myCustomApply(block: this.() -> Unit): this {
        block()
        // Do something else
        return this
    }
}

class B : A() {
    fun anotherFunction() {
        // Do something
    }
}

fun doSomethingVeryUseful(): B {
    val b = B()
    return b.myCustomApply {
        anotherFunction()  // YEAH
    }
}

Note that this is used in two places : as a receiver type and as a return type. As this already is a keyword, it wouldn't break any existing code, so backward compatibility is assured.

this as a type would have a single accepted value, that is, this. Why use this as a type ? This will garantee to the compiler that the value is effectively of the type of the receiver, that is b.myCustomApply now accepts a lambda of type B.() -> Unit, and has a return type of type B, since b itself is of type B.

Another (purely aesthetic) advantage would be that it is now clear that the same object (that is, the receiver) will be passed to the lambda, and then returned to the caller of the method.

This would translate to something like this :


abstract class A<_this : A<_this>> {
    private fun _this(): _this =
         @Suppress("UNCHECKED_CAST") (this as _this)        
    
    fun myCustomApply(block: _this.() -> Unit): _this {
        _this().block()
        // You got the idea
        return _this()
    }
}

class B : A<B>() {
    fun anotherFunction() {
        // Do something
    }
}

The _this type parameter's name is pretty self-explaining of its role, if A already has type parameters then _this should be added at the beggining of them, and any type expression of the form A<T, U, V, ...> should expand to A<A, T, U, V, ...>.

this as return type would require that return this and return /* something that has this as type */ are the only return statements that are allowed, as we couldn't ensure that any other value is actually always of the same type as this.

I propose this (what a great pun huh) because it has been several times that I felt a need for such a feature, but I'm unsure whether this is a good idea or not, if it is feasible, and if there actually are people that are interested.

In either cases, thank you for reading, have a great day !

2

There are 2 best solutions below

0
Jeff Bowman On

On thread "Self Types", from Andrey Breslav, lead language designer for Kotlin, in December 2014:

Unfortunately, self types turn out to be a challenge when it comes to type system design. We don't see a sane way of implementing them at the moment

See also:

1
broot On

It does not really answer your question (assuming there was any question at all), but note you can do something like this:

abstract class A<T : A<T>> {
    fun myCustomApply(block: T.() -> Unit): T {
        (this as T).block()  // block() would have the same effect
        // Do something else here
        return this
    }
}

class B : A<B>() { ... }