Self Types


#1

In a 2012 roadmap I saw a reference to supporting "Self types"

I see that the IDE seems to support the syntax, but the compiler doesn’t. Is this still in the works?

-N


#2

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


#3

I recommend looking at how Swift implemented Self :slight_smile:


#4

Swift, unlike Kotlin, does not have to interoperate with the JVM.


#5

@yole Is it not possible to implement it as recursive generic Foo<Self: Foo<Self>> under the hood? That already does what we want, except enforcing we actually use the same class name (Bar: Foo<Bar>) which would be a non-issue in generated code.


#6

Why support self types, when they are so tricky and foreign to JVM?

Just add This as some kind of typealias (local).
In current class body and in generic definitions for current class supertypes it expands to
CurrentClassName<With, Optional, Generics>
But when used as generic parameter for current class it expands only in that place to
This: ClassName<This, Probably, Other, Parameters<This>>
and in other behaves like generic parameter for current class

Should be enough to implement recursive generic (mentioned above) without noisy boilerplate.

Example:

interface A<This, R>         // interface A<This: A<This, R>, R>
class B : A<This, Any>       // class B : A<B, Any>
class C<This> : C<This, Any> // class C<This: C<This>> : Example<This, Any>

#7

I’d like to circle back and start a discussion on this idea that This (or some other token) can be used as a call-site alias to approximate self types for a large percentage of use cases. Suppose we take the following semantics:

  • Methods returning a This type in interfaces or open classes must return itself
  • Methods returning a This type in closed classes must return a value with the same type as itself.
  • From the perspective of the method caller, the return type is the same as the object itself

For example:

open class Foo<R> {
    fun foo(): This {
        // Methods in open classes must return itself
        return this
    }
}

class Bar : Foo<Int>() {
    fun bar(): This {
        // Closed classes can return any instance of the same type
        return Bar()
    }   
}

val bar = Bar()
val a = bar             // Type: Bar
val b: Foo<Int> = bar   // Type: Foo<Int>
val c: Foo<*> = bar     // Type: Foo<*>

// Method return type equals the type of the object as seen at the call site
val aCopy = a.foo() // Type: Bar
val bCopy = b.foo() // Type: Foo<Int>
val cCopy = c.foo() // Type: Foo<*>

This implementation should be purely syntactic sugar at compile-time, and should be implementable without major changes to the bytecode. Additionally, this should not impose any significant limits on any future implementation of self types. Meanwhile, it would ease the creation of things like fluent APIs and copy methods.


#8

I believe that the problem is not in syntax, but in type erasure. It is quite easy to use self type for non-generic class. In the worst case one needs to redefine a function for all inherited classes, but for generics it usually not possible to know specific generic type at compile time.


#9

@darksnake The proposal I described above should be doable in the presence of type erasure, as it is strictly a compile-time construct. Code calling methods returning This already know or can infer one of the supertypes of the actual object.

One possible implementation could be to have the compiler generate type casts at the call site of This methods. So long as the three rules I laid out above are followed, these casts should be safe and transparent to the programmer.


#10

I understand what you are saying, but it is not that simple. Let us consider following situation: we have some class A implementing your construct (e.g. fun self(): This) and its descendant B. Now we write something like this:

val a: A = B()
a.self()

since method self is generated in compile time it will return an instance of B not A. Maybe it is what we really needed, but it is not so obvious. By the way, it could be easily done by extension functions.

Another example, a little bit closer to reality. We have some tree node Tree which could provide a list of its children:

class Tree{
  fun getChildren(): List<Tree>
}

We want to have statically typed check that ensures that all of the children have the same type as tree or its descendant. We can use your This construct, but we will find that if TreeB inherits TreeA, List<TreeB> does not inherit List<TreeA>.

I don’t say that something reasonable could not be done about it, just that it is not that simple. I believe there was a discussion here and one of Kotlin developers said that self types are being currently considered by developers.


#11

Thanks for your response! Some comments:

I realize that self will return an instance of B. However, any instance of B must also be a valid instance of A, as it was successfully assigned to a. In this case, the result of a.self() can continue to be used as A, even if the actual type is B. For the example above, the code can be translated to the following:

val a: A = B()
(a.self() as A)

The intent is not to have a.self() suddenly be useable as B but rather continue to be A. (Perhaps “self type” is not the correct terminology here? I’m not familiar with how self types work in other languages, so I may be misusing the term).

The use case I had in mind for this makes extension functions a less desirable alternative, as it’s little better from overriding the method in every sub-class to update the return type.

I agree that such an example would require proper self types to function, and I recognize that this is something that is being considered. My proposal above would not support such a use case, as it only supports methods returning This by itself.

Overall, my goal is to explore whether a subset of self type functionality can be easily implemented in a way that is compatible with a future full implementation. Given the challenges with self types on the JVM, it doesn’t seem like true self types are coming anytime soon. Is there a way to obtain some of the benefits now?


#12

I just found an article about different approaches to self types in Java (and therefore in JVM). When I was challenged with the same problem, I used an approach called “recursive generics” in this article. I think Kotlin sealed classes could somehow simplify that approach, but of course Kotlin could not overcome JVM limitations without some very complicated constructs.


#13

Indeed. Recursive types are a common way around this, and is actually how Java Enum works. Another commenter also mentioned them earlier in this thread.

I currently use recursive generics to resolve this issue in my code, but there are a handful of issues. The biggest one is that classes with recursive generic parameters can’t be directly instantiated (while keeping generic constraints) because you can never satisfy the recursive parameter without a concrete subclass.

Another method is to simply give up on types, return Object, and cast the result to the correct type (see Java’s clone). This takes advantage of the full flexibility of type erasure. Safety in this setup requires that everyone follow a particular convention (which never happens). My proposed construct is an idea to formalize and type check this convention. It won’t get you general-case self types, but it can certainly remove a lot of existing ugliness.

Edit: Kotlin’s clone also returns Any. Imagine if it could return a self type instead…


#14

There is another problem with the recursive generics, that is exposed when you have multiple generics. In that case you end up with generics overload.