79 lines
3.0 KiB
Markdown
79 lines
3.0 KiB
Markdown
# Sealed Class Hierarchies and when-expressions
|
|
|
|
Goals:
|
|
* Make sealed class hierarchies possible (this implies more exhaustiveness criteria for `when`-expressions)
|
|
|
|
## Discussion
|
|
|
|
### Sealed Class Option
|
|
|
|
**This option is chosen as final.**
|
|
|
|
Special `sealed` keyword is introduced to mark base classes for sealed hierarchies, like this:
|
|
|
|
``` kotlin
|
|
sealed class Type () {
|
|
class Named(val name: String) : Type()
|
|
class Nested {
|
|
class Function(val param: Type, val result: Type): Type()
|
|
}
|
|
object Top: Type()
|
|
}
|
|
// ...
|
|
when (type) {
|
|
is Named -> println(name)
|
|
is Nested.Function -> println("$param -> $result")
|
|
// Alternatively, we can omit is here
|
|
is Top -> println("TOP")
|
|
}
|
|
```
|
|
|
|
It's assumed here that sealed class can be subclassed only by nested classes or objects, both with any level of nesting,
|
|
but not by local classes of any sort. So the `when` example would operate correctly.
|
|
|
|
Sealed classes are abstract by default, so abstract modifier is redundant
|
|
(otherwise `when` requires additional case covering all other cases: is Type).
|
|
Sealed classes can never be open or final.
|
|
Sealed interfaces are prohibited, otherwise Java classes could easily inherit them.
|
|
Sealed objects are also not possible, because we cannot inherit from object.
|
|
|
|
### Private Constructor Option
|
|
|
|
Another option would be to make `when` understand hierarchies like this one:
|
|
|
|
``` kotlin
|
|
abstract class Type private () {
|
|
class Named(val name: String) : Type()
|
|
class Function(val param: Type, val result: Type): Type()
|
|
object Top: Type()
|
|
}
|
|
```
|
|
|
|
When the superclass has a private constructor, all its subclasses are known to be (directly or indirectly) nested into in,
|
|
and we can be sure that a set of `is`-checks is exhaustive if all the subclasses are included:
|
|
|
|
``` kotlin
|
|
when (type) {
|
|
is Named -> println(name)
|
|
is Function -> println("$param -> $result")
|
|
is Top -> println("TOP")
|
|
}
|
|
```
|
|
|
|
However, class with a private constructor can also be derived as a local class, that provides a problem for this option implementation.
|
|
|
|
## Future When Optimization
|
|
|
|
It's possible to optimize when on sealed in the way like when on enum or when on string.
|
|
There are the following opportunities, all of them use `KSealed` interface with some `final` identification method:
|
|
|
|
* use `ordinal()` method, which is implemented like enums, so the first descendant has an ordinal 0,
|
|
second 1 and so on. When on sealed organized like when on enum. Drawback: reordering breaks client's code.
|
|
* use `sealedName()` method returning a fully qualified class name of a direct sealed descendant.
|
|
When on sealed organized like when on string. Drawback: extra efforts.
|
|
* use `sealedId()` method returning a hash code of a direct sealed descendant fully qualified class name.
|
|
Drawbacks: possible collisions, including an opportunity to rename some member and get hash code of another member,
|
|
which breaks client's code in a dramatic way.
|
|
|
|
After optimization, `instanceof KSealed` should be checked at run-time before applying it.
|