File: sealed-hierarchies-and-when.md

package info (click to toggle)
kotlin 1.3.31%2Bds1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 109,908 kB
  • sloc: java: 454,756; xml: 18,599; javascript: 10,452; sh: 513; python: 97; makefile: 69; ansic: 4
file content (78 lines) | stat: -rw-r--r-- 3,041 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# 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.