Data classes should be able to inherit from each other


#23

I think you would also get those things if only the children are data classes.


#24

I know there is a lot written and said about mixed type equality. But the simplest (and to me most logical) solution would just be to only match equal if they are the same class type AND have logically equal properties.

So B inherits A, an instance of B can never equal an instance of A no matter what their common properties are.

That seems like a good default. And if people do need mixed type equality, they can implement it in a way that makes sense for their use case.


#25

The abstract class with data class strategy I described does work, however, it has another draw back I’m discovering.

For easy migration, I attempted to use the @JvmField annotation, however because the properties are declared overriden, it can not be used. So more and more this strategy seems quite bad.

If we did get data class inheritance, we could use normal non-abstract or overriden properties, and thus these annotations would work.


#26

@Wavesonics I agree with you. This behavior makes sense to me, instances from different classes shouldn’t be equal at all.


#27

Man I just keep running into this problem. I actually wish data classes could ONLY inherit from other data classes.

Here is a concrete case I just encountered:

I have message classes passed between client and server (shared code base, so awesome job there Kotlin!)

This is what I want:

open data class NetworkResponse(val responseCode: Int)
open data class GameUpdate(val gameState: GameState, responseCode: Int) : NetworkResponse(responseCode)
data class PlayerJoinUpdate(val newPlayerId: Int, gameState: GameState, responseCode: Int) : GameUpdate(gameState, responseCode)

However, since data classes currently can’t inherit from each other, I have a nasty problem. Only the Leaf nodes in my inheritance tree can be data classes. Which almost makes sense, except in my scheme here, non-leaf nodes such as GameUpdate are still totally valid messages to be sent on their own.

What I have had to do is something more like this:

abstract class NetworkResponse(open val responseCode: Int)
open class GameUpdate(open val gameState: GameState, override val responseCode: Int) : NetworkResponse(responseCode)
data class PlayerJoinUpdate(val newPlayerId, override val gameState: GameState, override val responseCode: Int) : GameUpdate(gameState, responseCode)

Now I have to manually add in all of the data class provided methods into the GameUpdate class which is not fun. And of course multiply this out across the entire protocol, there are a good number of classes I need to do this for, and we’re back closer to Java land, where I get lazy and don’t do it.

Also notice that in this scheme I have to make the properties open, and override them in child classes. I’d much rather just be able to take them in as arguments and pass them to the parent ctor as in my first example.

I think there are some really good use cases for data class inheritance.

I know ilya.gorbunov was concerned about what equality would mean in a world that allowed inheritance, and I think it would be simplest and most useful if it was the strict equality like I mentioned earlier: Only classes of the exact same type, with logically equivalent properties, would be equal to each other. Anything more complex than that, dealing with child classes or what ever, can just be left to the user to implement on their own.

Exmaple:

override fun equals(other: Any?): Boolean
{
    if (this === other) return true
    if (this.javaClass != other?.javaClass) return false
    other as GameUpdate
    if (this.responseCode != other.responseCode) return false
    if (this.gameState != other.gameState) return false
    return true
}

#28

Thanks, this example is helpful.

What methods of a data class do you miss most in these open classes? Is it componentN for destructuring? The copy function? Or toString()?


#29

These are the most important to me:

  • equals important for some of my client side logic, dedupping
  • toString for debugging is very nice

Less so, but would be nice:

  • copy I don’t implement this currently, but if I had it for free I’d probably use it for mutating my immutable classes.
  • hash less so for my particular use case, but probably important if I ended up using them as a key in a hashmap or something.

I haven’t used the destructing stuff for my data classes yet.