can anyone explain why data classes can't be inher...
# getting-started
b
can anyone explain why data classes can't be inherited from? I know there is some kind of issue with regards to the type system of how equals/hashcode are generate but I don't know the exact details
1
b
ok, so let's assume you've got 2 data classes, one containing an address and one containing username and email; you want a data class that merges and flattens both without simply nesting those 2 into a new object; I can't see an obvious reason why you couldn't generate copy and equals/hashcode in that case
h
Here's one problem I see with this: Let's say we have two data classes, one inheriting from the other:
Copy code
data class Person(val name: String, val age: Int)
data class Employee(val department: String, name: String, age: Int) : Person(name, age)
Now how would one use destructuring?
Person::component2
has a different signature than `Employee::component2`:
Copy code
fun doSomething(friend: Person) {
    val (name, age) = friend
    println("$name is $age year old")
}
doSomething(Employee("math", "Ben", 15))
1
b
not 100% sure but I think Kotlin conf had some talks about name based destructuring
positional destructuring is very error prone anyways, unless you are using it for things like tuples
JS for instance uses const {a:b, c:d} = object
h
I've heard rumours about named destructing too. And I agree that this problem is not entirely with inheriting from a dataclass, but also with positional destructuring, but it is something that needs a solution before inheriting from a data class can work.
b
the general use case I think is less about inheritance and more about creating a sort of product type
as in: a data structure that contains a set of fields taken from other data classes
g
b
right now to achieve that you need to use interfaces and copy paste all fields from the relevant data classes into the new one
w
I see that we're shifting away from talking about data class inheritance, but there's many footguns with them. One of them being: The contract of
equals
states that
(x == y) == (y == x)
must always be true. One of many things that would break with data class inheritance. I think that the feature you're looking for is structural typing. Meaning that if some type
A
has all the functions of another type
B
, then you can safely cast
A
to
B
. For example, that would make
Sequence
and
Iterable
to be exactly the same type. TypeScript supports structural typing, and they consider `Employee > Person`:
Copy code
type Person = { name: String, age: number }
type Employee = { department: String, name: String, age: number }

function foo(person: Person) {}

const employee: Employee = { department: "Gift Delivery",  name: "Santa", age: 1713 }

foo(employee)
In your situation there's overlap in the behavior that both data classes have, which means there's 2 main options: • Make an interface with the shared behavior (like you suggested) • Make another data class that has the overlapping data. (
Employee(department: Department, nameAndAge: NameAndAge)
) I can imagine that both of them are suboptimal for your needs.
b
can you explain the issue with regards to equals? is it about 2 different classes which define the same fields and inherit from the same base classes not having a compatible equals implementation?
like Cat(val name: String): Animal and Dog(val name: String): Animal breaking when cat == dog?
h
Following my definition of
Person
and
Employee
from earlier, the problem @Wout Werkman describes with equals is (probably)
Copy code
val a = Person("Ben", 15)
val b = Employee("math", "Ben", 15)
a.equals(b) // true
b.equals(a) // false
☝️ 2
thank you color 1
b
I see, there's a gotcha because of subtyping
the solution would probably be for both to return false if it's not the same leaf type but I can see this leading to confusion
would need to work around the existing type system as well
thanks, that was the part that I was missing 🙂
👍 1