Let's say that I have a sealed interface, and each implementation of the interface is a data class
sealed interface Animal {
val name: String
val age: Int
}
data class Cat(
override val name: String,
override val age: Int,
) : Animal
data class Dog(
override val name: String,
override val age: Int,
) : Animal
// Possibly many more animals
I want to have a function which let's me rename any Animal and return the same type of Animal.
fun <T : Animal> T.rename(newName: String): T
It's surprisingly difficult to come up with an implementation for this. The best I could do is this, which is extraordinarily redundant and results in a compiler warning Unchecked cast: Animal to T even though we know this cast is guaranteed to succeed.
fun <T : Animal> T.rename(newName: String): T = when(this) {
is Cat -> copy(name=newName)
is Dog -> copy(name=newName)
// and so on for many other animals
else -> throw IllegalStateException(::class.toString())
} as T
Another thing I tried is to add copy(name: String) to Animal, but the copy function generated by Kotlin doesn't count as an implementation.
sealed interface Animal {
val name: String
val age: Int
fun copy(name: String): Animal
}
// Class 'Cat' is not abstract and does not implement abstract member public abstract fun copy(name: String): Animal defined in com.example.Animal
data class Cat(
override val name: String,
override val age: Int,
) : Animal
Seems like I ran into a limitation of Kotlin's type system. Is there any way to implement the rename function without manually implementing it for each subtype of Animal? And without casting?
The full signature of the data class
copyfunction would look similar to this:But this causes another error:
In fact, it's not possible for the
copyfunction to be an override: see discussion and YouTrack issue.Instead of using a
whenblock, you can add thecopyto the data class using a different function name:Unfortunately, this doesn't buy you much, as it just moves the code duplication to a different place.
If you want to keep your current code as it is, note that when you use a sealed interface, the
whenis exhaustive, so you don't need anelsepart, and you're guaranteed you won't miss a subtype.