I found something distressing after update to M14. At first I completely agreed with data class limitations mentioned in one of your blog posts. I thing it's a good idea to prohibit inheritance with data classes but I think you should also dissallow using vars in them. But that's not my point right now.
There is currently one use case for which I think it’s absolutely reasonable and favorable to allow data class to inherit from other class. I really hope this was just overlooked because sealed classes are not used that much yet. But I am using them since the first day they appeared at master branch because I think it’s one of the most powerfull features that was added to the language. But now is my code full of warnings about data class inheritance.
Example:
sealed class Size {
object Small: Size()
class Big(val x : Int): Size()
}
fun main(args: Array<String>) {
val sizes = setOf<Size>(Size.Small, Size.Small)
println(sizes.size()) // prints 1 – OK
val sizes2 = setOf<Size>(Size.Big(1), Size.Big(1))
println(sizes2.size()) // prints 2 – Not OK
}
Code now doesn’t work as expected because Size.Big can’t have “data” modifier and doesn’t have correctly defined equals/hashCode. I can define them by hand and enjoy this java-like experience or just return where I was before M13 and use interface instead.
interface Size {
object Small: Size
data class Big(val x : Int): Size
}
But now I lost exhaustive checking and I'm sad.
Maybe I’m just missing something but sealed classes seems broken to me in current situation.
Two possible solutions came to my mind. Relax your restrictions of data clasess and allow them to inherit from sealed class if it doesn’t have any properties or all properties are abstract. Or allow new syntax: “sealed interface { … }”.
I have second question just because I’m curious. Is your current design of “algebraic sum type” something you deliberately wanted or this is result of some compromises? How to represent this in bytecode, interoperability with Java, or something else? Because since the beginning I was little worried about explicit usage of inheritance and now it culminated with this problem with data classes. Why didn’t you just gave more power to enum and allow its values to have arguments? I mean something like enum in Swift. Because there is sometimes strange overlap between these 2 things:
enum class Enum {
A, B
}
sealed class Sealed {
object A: Sealed()
object B: Sealed()
}
Both enum and sealed classes can now represent "the same" concept. Why not merge these 2 things into only one language feature? Sealed class is also much more verbose and contains explicit inheritance (in enum it's hidden behind the curtain). I noticed that because of this it's also harder to explain to java developers what sealed class actually represents (algebraic type) because all they see is just inheritance of classes.
Another thing that I don’t like that much is how type inference works.
val x = Enum.A // inferred type Enum
val y = Sealed.A // inferred type Sealed.A
I find the enum inferrence much more intuitive and convenient.