Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a proposal for "data objects" #316

Merged
merged 9 commits into from
Sep 12, 2022
82 changes: 82 additions & 0 deletions proposals/data-objects.md
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)

strangepleasures marked this conversation as resolved.
Show resolved Hide resolved

## 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
strangepleasures marked this conversation as resolved.
Show resolved Hide resolved
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
strangepleasures marked this conversation as resolved.
Show resolved Hide resolved

### 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
elizarov marked this conversation as resolved.
Show resolved Hide resolved
* `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