Self Types

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

11 Likes

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

1 Like

I recommend looking at how Swift implemented Self :slight_smile:

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

1 Like

@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.

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>
1 Like

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.

3 Likes

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.

@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.

1 Like

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.

1 Like

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?

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.

3 Likes

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…

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.

1 Like

The self type is definitely possible with a JVM language. Manifold provides the Java language with the self type via @Self:

The simple case:

public class Foo {
  public @Self Foo getMe() {
    return this;
  }
}

public class Bar extends Foo {
}

Bar bar = new Bar().getMe(); // Voila!

Use with generics:

public class Tree {
  private List<Tree> children;

  public List<@Self Tree> getChildren() {
    return children;
  }
}

public class MyTree extends Tree {
}

MyTree tree = new MyTree();
...
List<MyTree> children = tree.getChildren(); // :)  

Use with extension methods:

package extensions.java.util.Map;
import java.util.Map;
public class MyMapExtensions {
  public static <K,V> @Self Map<K,V> add(@This Map<K,V> thiz, K key, V value) {
    thiz.put(key, value);
    return thiz;
  }
}

var map = new HashMap<String, String>()
  .add("bob", "fishspread")
  .add("alec", "taco")
  .add("miles", "mustard");

The issue was never primarily the JVM. The issue is that Java does not support it so you cannot easily make it compatible/usable from Java. Other languages may not have the strong compatibility goals that Kotlin has, but for Kotlin that is a big limit.

Introducing self types would not be breaking change, so I hope it will be introduced some time in future, when Kotlin will need to think less about backward Java compatibility.

1 Like

The self type is no less compatible with Java than Kotlin’s declaration site generics. Why single out self types?

While I would also really like a Self type/alias, the semantics of this are not expressible in Java. Declaration site generics open a small gap for incorrect use in Java, but types which use them are fully Java compatible. A self type can be approached by a recursive generic (with all the pain that entails), but is actually constrained more tightly. It would not be possible to specify the constraint in Java such that Java classes extending the self-typed one could invalidate the invariant (not return self-type values etc). It may even be impossible to correctly extend such a type from Java (which is more of an issue).

Ok, I’m gonna fight you a little bit here, but it’s all in fun! When you say “extend such a type” I assume you mean override a method that uses the self type, right? I would think Kotlin would express the method in bytecode using erased types, thus Java could surely override the method. Of course using the Java method from Java may involve casting for lack of self type support in the language, much the same way Java uses wildcards and casting when using a Kotlin class employing declaration-site variance. But using the Java method from Kotlin could just work because the compiler can overlay the type information necessary to make it happen.

All of this depends on how the type system implements the self type. My assumption is that it is implemented as a type annotation or modifier where say self internally resolves as the enclosing type annotated with “self”. As such the compiler infers the type based on the call site context i.e., there is no need for binary effects such as method bridging.

In any case, recursive generics solves the problem and works with Java. And it’s a niche feature, so I can understand the hesitation.