-
Notifications
You must be signed in to change notification settings - Fork 362
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #316 from strangepleasures/patch-1
Add a proposal for "data objects"
- Loading branch information
Showing
1 changed file
with
82 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
# Data objects | ||
|
||
* **Type**: Design proposal | ||
* **Authors**: Alexander Udalov, Roman Elizarov, Pavel Mikhailovskii | ||
* **Status**: Experimental since 1.7.20, stable since 1.8.0 | ||
* **Discussion and feedback**: [#317](https://github.com/Kotlin/KEEP/issues/317) | ||
|
||
|
||
## Introduction | ||
|
||
When using algebraic data types, it’s annoying to declare singleton values because there’s no way to get automatic toString similar to data classes. E.g. on JVM: | ||
```kotlin | ||
sealed class MyList<T> { | ||
data class Cons<T>(val value: T) : MyList<T>() | ||
object Nil : MyList<Nothing>() | ||
} | ||
|
||
fun main() { | ||
println(MyList.Cons(42)) // Cons(value=42) | ||
println(MyList.Nil) // test.MyList$Nil@1d251891 | ||
} | ||
``` | ||
|
||
A workaround for the user is either to declare `toString `manually, which leads to boilerplate, or use data class with a placeholder default argument, which seems bizarre. | ||
|
||
## Other aspects | ||
|
||
* Default toString implementation differs on JVM, JS, Native | ||
* In JS, toString returns [object Object] | ||
* Native behaves similarly to JVM, but inner classes are delimited with ., i.e. test.List*.*Nil@1d251891 in the example above | ||
* The @hash part is meaningless even for non-ADT objects, because object is a singleton | ||
* Can’t declare an empty @JvmRecord class in Kotlin (KT-48155 (https://youtrack.jetbrains.com/issue/KT-48155/JvmRecord-does-not-allow-one-to-define-no-parameters)) | ||
* Probably not a problem | ||
|
||
### toString for all objects | ||
|
||
One possible solution is to generate toString for all objects unconditionally, which returns the simple (unqualified) name of the object. | ||
|
||
The advantage is that it’s more uniform, and the least surprising for the users. | ||
|
||
There are some issues though: | ||
|
||
* It’s likely a major breaking change | ||
* It leads to extra bytecode which is often unnecessary | ||
* Simple name instead of FQ name might be confusing, especially for cases like companion object | ||
|
||
### Data object | ||
|
||
Another possible solution is to allow the data modifier on objects, which will lead to `toString` being generated as for data classes. | ||
|
||
Even though data objects are singletons, `equals` and `hashCode` probably should also be generated: | ||
* it may be useful to have a stable `hashCode` | ||
* as it's in principle possible to create multiple instances of a `data object` class via JVM reflection, and some third-party frameworks may do that, | ||
it would make sense to have `equals` implementation that would treat all instances of the same data class as equal. | ||
|
||
Other data class methods should not be generated: | ||
|
||
* `componentN` makes no sense because it doesn’t have a public constructor | ||
* `copy` makes no sense because it’s a singleton | ||
* Or should it be generated and always return this? | ||
|
||
Question: data companion object | ||
|
||
Question: could Unit be redefined as data object? | ||
|
||
## The Proposed Solution | ||
|
||
* Support `data object` syntax in all frontends & backends | ||
* Generate equals, hashCode, toString for data objects | ||
* `equals` should only check whether `this` and the other object are of the same type | ||
* `hashCode` returns the value of the hash code of the qualified class name | ||
* `toString` returns the simple name of the object | ||
* On JVM: if data object is serializable, generate `readResolve` which returns the value of the INSTANCE field | ||
* Prohibit to declare or inherit custom `equals`/`hashCode` (but not `toString`!) in data objects | ||
* It’s OK if there’s a non-final implementation of `equals`/`hashCode` in a superclass of a data object, since it’s overridden anyway | ||
* `DataObject::class.isData` should return `true` | ||
* Prohibit `data companion object` syntax and use in anonymous object expressions, e.g. `val o = data object {}` | ||
* After kotlin-stdlib is compiled with language version 1.8, make `Unit` and `EmptyCoroutineContext` data objects | ||
* IDE (KTIJ-22087 (https://youtrack.jetbrains.com/issue/KTIJ-22087/Support-IDE-inspections-for-upcoming-data-objects)) | ||
* Existing inspection that suggests to add data to a sealed subclass should also do it for objects now | ||
* Add an inspection to recommend to add data to a serializable object | ||
|