I don’t think that self types are so important any longer. To be honest in Kotlin it’s much better to use DSLs instead of fluent APIs like in Java:
interface VehicleBuilder {
var name: String // no need for self types
}
interface CarBuilder : VehicleBuilder {
fun gearBox(gearBoxBuilder: GearBoxBuilder.() -> Unit): GearBox
}
val car = buildCar(requiredProp1 = 1, requiredProp2 = 2) { // this: CarBuilder
name = "my pet porsche"
gearBox { // this: GearBoxBuilder
gearsCount = 6
type = GearBox.MANUAL
}
}
instead of:
Car car = Car.builder(1, 2) // (Java only) no parameter names
.name("my pet porsche") // has to return CarBuilder
.gearBox()
.gearsCount(6) // requires some formatter hacks to keep indents for muti-level builders
.type(GearBox.MANUAL)
.buildGearBox() // has to return CarBuilder
.build()
I met this problem several types too in my job. Well, most of my projects were not in favour of switching to Kotlin so I had to stick to this retro Java builder style
That or an annotation. I kind of like that idea of being able to use more powerful constructs when java interop is not needed in a specific part of the code.
I see usecases here which revolve around returning the self-type from a subclass or a class implementing an interface.
My use case is that I would like an abstract method defined in a base class to return the self-type when called from any subclass. The compiler would need the implementations to return this.
This would be quite useful for data classes, because this would allow having one method to copy() the instance with a different value of a field defined in the base sealed class. For now, I need to copy the same code into each data subclass.
I also find this quite useful in several cases. Here an example that I just ran into:
interface Entity {
val creationTimestamp: Timestamp
val isDeleted: Boolean
fun copy(
creationTimestamp: Timestamp = this.creationTimestamp,
isDeleted: Boolean = this.isDeleted,
): Self // Example 1
fun markDeleted(isDeleted: Boolean): Self // Example 2
}
I’m aware that it can break in Java but I don’t think that it’s a big deal if
Java is irrelevant (e.g. if this isn’t a library and doesn’t involve Java code anyway, or is a library but doesn’t support Java anyway)
Java is relevant but the semantics are clearly documented (i.e. the function must return an instance of the implementing class or a subclass, and every subclass must override the function)
The downsides when it comes to Java interop are the same as with sealed interface which is currently in development for 1.5. You can also implement that interface in Java although it’s “not allowed” because it’s sealed. It’s an acceptable trade-off.
Are there any use cases for this feature that don’t involve forcing methods to return this?
The caller, of course, already has this object, since it just called a method on it. Forcing methods to return it seems silly. If you want to change the language just to enable fluent calling syntax, then surely that is more appropriately handled with a simple syntactic change instead of a complicated change to the type system.
The one place this is useful if you want to use the type of your current class as a generic parameter.
One example from a private project of mine is something like this
abstract class Asset<out A: Asset<A>> {
abstract val metaData: AssetMetaData<A>
// ...
}
Here I would prefer to just write something like
abstract class Asset {
abstract val metaData: AssetMetaData<Self>
}
Having a self type would reduce the complexity of the generics around my asset system by a lot.
Another example (although a constructed one) is something like this
abstract class Foo {
fun combineWith(other: Self): Self
}
This could be used for the Number class to at least define math operators between the same number type. While it’s not possible to use something like
abstract class Number {
operator fun plus(other: Number): Number
}
because you can’t possibly implement addition between all possible number types something like
abstract class Number {
operator fun plus(other: Self): Number
}
That is the CRTP – popular in C++, but not in Java, because it’s not nearly as useful without reified types. This looks like a valid use of the CRTP, but I don’t think a Self type would really do what you want here. Only the class that actually specifies the type parameter could possibly set metadata, because no other type would know what kind of instance to create. If you had a Self type, then the implication would change so that only closed subclasses could possibly set metadata. That’s pretty weird.
Similarly, any such plus method would be impossible to implement unless there was something to ensure that the implementation wouldn’t be inherited. Since you would have to supply a Self parameter, it would also be impossible to call unless the receiver is a concrete closed type… and in that case the interface method is useless, anyway.
One feature of Self (result) types would be, that the compiler will enforce subclasses to provide an override method, unless the method returns this (or the result of another Self-method). For methods like copy and metadatathis actually makes sense and can prevent coding errors.
For the copy use case, I think that Self types just introduce LSP violations: A caller has a Base reference, but somehow depends on the fact that Base.copy(), when called on Subclass, will return a Subclass instance? That’s not right.
It is better just to use covariant returns, which the language already provides: Base has copy() : Base and the override in Subclass has copy(): Subclass, so if you have a Subclass reference, you can call copy and the compiler will know that you get a Subclass result.
The ability to prevent inheritance of a given method would be valid and useful in several contexts, I think, but not this one. It would be nice if there was a keyword to do that no matter what the return type was. It might be good to use that on the first override of hashCode and equals. Why trick the type system into doing this job?
abstract class State {
open fun getStatesToPropagate(isInitialization: Boolean, previousState: State): List<State> {
if (isInitialization || previousState != this) return listOf(this)
return listOf()
}
}
previousState should be of the same type as the class that inherits from State, so for a class CounterState I’d have something like this
class CounterState(val count: Int): State {
fun getStatesToPropagate(isInitialization: Boolean, previousState: CounterState): List<State> {
val list = arrayListOf<State>()
if (previousState.counter > 155 != counter > 155 || isInitialization) list.add(CounterSubState(isGreatEnough = counter > 155))
list.addAll(super.getStatesToPropagate(isInitialization, previousState))
return list
}
}
This would assure that callers of getStatesToPropagate need to pass a state of the correct class and that the classes that implements getStatesToPropagate don’t need to cast previousState to the correct class.
Ignoring for a moment that this is definitely building in a violation of the Liskov Substitution principle, I have to ask why? I have written a lot of state machines over the years, and whenever I’ve gone to the trouble of making State classes, it is precisely because it allows me to use different implementations of it for different states in the state machine.
I think you actually want all of your states to have a common base class/interface that, if necessary, should be a type parameter of the State interface.
Can’t see why code is in a violation of the liskov Substitution principle, you still can write it with generic :
open class State<T : State<T>> {
open fun getStatesToPropagate(isInitialization: Boolean, previousState: T): List<T> {
if (isInitialization || previousState != this) return listOf(this as T)
return listOf()
}
}
And
class CounterState<T : CounterState<T>>(val count: Int): State<T>() {
override fun getStatesToPropagate(isInitialization: Boolean, previousState: T): List<T> {
val list = arrayListOf<T>()
//if (previousState.counter > 155 != counter > 155 || isInitialization) list.add(CounterSubState(isGreatEnough = counter > 155))
list.addAll(super.getStatesToPropagate(isInitialization, previousState))
return list
}
}
Functions that involve Self are never inherited and must be overridden.
It could be established that two values are of exactly the same final type using generics.
interface Number { operator fun plus(other: Self): Self }
fun <@Final T: Number> add(left: T, right: T) =
left + right
Here the caller has to make sure that add is called with two values of the same final type, e.g. add(1, 2) or add(complex(1,2), complex(3,4)). The compiler could also support if (a::class == b::class) add(a,b) which would work for non-final types too.
Why not? Subtypes are free to return covariant types. The only difference here is that a supertype forces subtypes to return a specific covariant type.
That requires casts in generic contexts. If I know that T: Base and I call fun copy(): Self then I know that the return type is T and not Base. That doesn’t work with covariance.
Thanks for that. It seems that self types are useful in classes/interfaces when they are used in type constraints, even though they may not be useful when those classes/interfaces are used as types.
I still hate them, but you gave me what I asked for
1.) as annotation + compile time reflection analogous to the manifold plugin of @rsmckinney
2.) as implicit type parameter (proper solution)
The first would
a.) automate overriding methods for covariant return type specialization
b.) and forces the user to downcast the object of more general type than Self in case of self parameters
However, on the Java side, these contracts can easily be broken up.
The second solution introduces a Self parameter, a special keyword akin to this, or maybe we prefer This over Self more akin to this.
It serves mainly the same logic as described for 1.) except that Java sees a Kotlin class equipped with Self type parameters as a normal generic Java class with recursive type parameter constraints, the way Self types are usually solved in Java.
Just to summarize the main benefit is the builder pattern:
open class Animal<out This: Animal<This>>
{
private var name:String="Animal"
private var weight:Double=20.0
fun setName(name:String):This
{
this.name=name
return this as This
}
fun setWeight(weight:Double):This
{
this.weight=weight
return this as This
}
}
open class Bird<out This: Bird<This>>:Animal<This>()
{
private var wingSize:Double=10.0
fun setWingSize(wingSize:Double):This
{
this.wingSize=wingSize
return this as This
}
}
private var bird:Bird = Bird().setName("Birdy").setWeight(200.0).setWingSize(20.0)
The last line doesn’t even work in Kotlin as raw types aren’t available in Kotlin as stated in:
But even then, you could initialize Animal<Bird> resulting into cast errors whenever the user calls a setter of this object.
Further, parametrizing builder classes feels like a hack only serving technical and not idiomatic purposes.
Since Kotlin doesn’t want to go the raw type route, introducing Self seems more appropriate and safe.
There is one use case for self types that is interesting as well. It allows a member (esp. polymorphic) version of the following extension:
fun <T: SomeClass> T.doSomething(): T = TODO("Some implementation")
While we can do this as extension implementation is still tricky in cases (when not using casts to override type safety). At the same time, this trick is a good way to avoid the need of self types where the extension method on a type variable works.