Data class “inheritance” is one of the most sought-after features of Kotlin. Just take a look at the amount of discussion and upvotes across these sites:
- Stack Overflow (Especially this question)
- Kotlin Forum
I’m aware that traditional inheritance for data class would cause many issues, such as inconsistent
componentN functions, ambiguous
copy signatures, impossible to define a correct
.equals implementation, and just generally breaking the Liskov substitution principle. Nevertheless, developers often want to compose their data models, often to “extend” / augment a data class with just one or two additional fields.
Current work-arounds for a lack of data class extensibility include:
- Declaring a base abstract class with shared fields, implementing the base class in two separate data classes.
- Declaring a base interface with the shared fields, implementing the interface in two separate data classes.
But these methods involve repeating the full set of fields in the base definition at least three times – once in the base class/interface, and once in both implementations. This is cumbersome and tedious, especially with large data models with dozens of fields, and makes the process to add new fields error-prone.
I would love to see Kotlin have some way to support data class composition without repetition, i.e. define a data class using all of the fields from another data class, plus some additional fields. Without being overly prescriptive, because I am open to any solution, I think one potential way to accomplish this is to define an
augments keyword, which embeds the parameters of another class’ constructor into a new class definition:
data class MyModel( val prop1: String, val prop2: String, ) data class MyAugmentedModel( val prop3: String, ) augments MyModel val augmented = MyAugmentedModel("prop1", "prop2", "prop3")
Note that there is no inheritance happening here. Any instance of
MyAugmentedModel will not also be an instance of
MyModel, because they are separate data class and can never be considered equal. It could be helpful to automatically generate extension functions to convert between these two overlapping models, however:
fun MyAugmentedModel.toMyModel() = MyModel(this.prop1, this.prop2) fun MyModel.toMyAugmentedModel(prop3: String) = MyAugmentedModel(this.prop1, this.prop2, prop3)
I am initially proposing
augments MyModel, instead of typical
: MyModel(), to avoid ambiguity with the existing syntax which defines inheritance and requires repetition of constructor parameters.
extends could cause confusion with Java’s
extends, which imlplies inheritance.
augments syntax as a way to create constructors that have all the fields of another constructor could be useful outside of the contexts of data classes as well, because repetitive constructors is not just a data class problem, but I haven’t thought enough about that yet.