From 4e1eb08cdc1b3a04437cf438650fe1f5b38deabb Mon Sep 17 00:00:00 2001 From: Nikolay Lunyak Date: Wed, 15 Sep 2021 20:53:02 +0300 Subject: [PATCH 1/6] Add explicit backing fields proposal --- proposals/explicit-backing-fields.md | 273 +++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 proposals/explicit-backing-fields.md diff --git a/proposals/explicit-backing-fields.md b/proposals/explicit-backing-fields.md new file mode 100644 index 000000000..7855f1744 --- /dev/null +++ b/proposals/explicit-backing-fields.md @@ -0,0 +1,273 @@ +# Explicit Backing Fields + +- **Type**: Design Proposal +- **Author**: ? +- **Contributors**: Svetlana Isakova, Kirill Rakhman, Dmitry Petrov, Roman Elizarov, Ben Leggiero, Matej Drobnič, Mikhail Glukhikh, Nikolay Lunyak +- **Status**: Implemented in FIR +- **Prototype**: Implemented +- **Initial YouTrack Issue**: [KT-14663](https://youtrack.jetbrains.com/issue/KT-14663) +- **Initial Proposal**: [private_public_property_types#122](https://github.com/Kotlin/KEEP/pull/122) + +## Summary + +**Note**: initial proposal contents have been partially copied down here for convenience. Despite the different approach, the already shown use cases are still relevant. + +> Common pattern in java is having full type accessible as private property and then only exposing required interface in the public getter: +> +> ```java +> class MyClass { +> // Use full type for private access +> private final ArrayList data = new ArrayList<>(); +> +> // Only expose what is needed in public getter +> public List getData() { +> return data; +> } +> } +> ``` +> +> This pattern allows easy hiding of implementation details and allows for easy clean external interfaces to the classes. But there is no idiomatic Kotlin way to achieve this pattern. Best thing you can do is define two separate properties to mimic how Java does it: +> +> ```kotlin +> class MyClass { +> private val _data = ArrayList() +> val data: List +> get() = _data +> } +> ``` + +With the proposed syntax in mind, the above code snipped could be rewritten as follows: + +```kotlin +class MyClass { + val data: List + field = ArrayList() +} +``` + +## Use Cases + +### Motivation + +> - There is no clean idiomatic way to do this pattern in Kotlin +> - Current best approach throws away all benefits of Kotlin properties and forces developer to write Java-like code with separate private field and public getter +> - Current best approach forces developer to assign two different names to single property (or pad private property, for example adding `_` prefix and then using this prefix everywhere in code) +> - This is another place where Java pattern could be introduced into Kotlin with less boilerplate + +### Read-Only from Outside + +> We often do not want our data structures to be modified from outside. Unlike Java, this can be easily achieved in Kotlin by just exposing read-only `List`. But as already ilustrated in above example, exposing different type of a property is a bit messy. + +```kotlin +private val _items = mutableListOf() +val item : List by _items +``` + +And the new syntax allows us to write: + +```kotlin +val items: List + private field = mutableListOf() +``` + +### Android Architecture Components + +> Proper way to do Architecture components is to use `MutableLiveData` (`LiveData` implementation that allows caller to change its value) privately inside View Model classes and then only expose read-only `LiveData` objects outside. + +Sample code [from an Android app](https://github.com/elpassion/crweather/blob/9c3e3cb803b7e4fffbb010ff085ac56645c9774d/app/src/main/java/com/elpassion/crweather/MainModel.kt#L14): + +```kotlin +private val mutableCity = MutableLiveData().apply { value = "" } +private val mutableCharts = MutableLiveData>().apply { value = emptyList() } +private val mutableLoading = MutableLiveData().apply { value = false } +private val mutableMessage = MutableLiveData() + +val city: LiveData = mutableCity +val charts: LiveData> = mutableCharts +val loading: LiveData = mutableLoading +val message: LiveData = mutableMessage +``` + +Becomes: + +```kotlin +val city: LiveData + field = MutableLiveData().apply { value = "" } +val charts: LiveData> + field = MutableLiveData().apply { value = emptyList() } +val loading: LiveData + field = MutableLiveData().apply { value = false } +val message: LiveData + field = MutableLiveData() +``` + +### RX Observable and Subjects + +> Data can often be pushed into reactive world using subjects. However, exposing `Subject` would allow consumer of the class to push its own data into it. That is why it is good idea to expose all subjects as read-only `Observable`: + +```kotlin +class MyClass { + private val _dataStream = PublishSubject.create() + val dataStream: Observable + get() = _dataStream +} +``` + +Turns into: + +```kotlin +class MyClass { + val dataStream: Observable + field = PublishSubject.create() +} +``` + +## Design + +The proposed design consists of two new ideas. + +### Explicit Backing Fields + +```kotlin +val it: A + [visibility] field[: B] = initializer +``` + +The above `field` declaration is referred to as an _explicit backing field declaration_. + +#### Accessors + +- if `A :> B`, the compiler can provide a default getter +- if `A <: B`, the compiler can provide a default setter +- If an accessor is required, it must be provided either by the compiler or the user + +```kotlin +public val flow: SharedFlow + field: MutableSharedFlow? = null + get() { + return field ?: run { ... } + } +``` + +#### Visibility + +Only the `private` and the `internal` visibilities are allowed for explicit backing fields now. The default visibility is `private`. + +```kotlin +val mutableWithinModule: List + internal field = mutableListOf() +``` + +Right now, there is no special syntax to explicitly access the backing field from outside the property accessors, but we will see how it can be accessed implicitly via the ["second idea"](#smart-type-narrowing). + +#### Lateinit + +If a property has an explicit backing field declaration, and it needs to be `lateinit`, the modifier must be placed at the `field` declaration. + +```kotlin +var someStrangeExample: Int + lateinit field: String + get() = field.length + set(value) { + field = value.toString() + } +``` + +#### Initializer + +If there is an explicit backing field, it must have an initializer, and the property must not declare an initializer. + +#### Forbidden + +Explicit backing fields are not allowed inside interfaces or abstract properties. + +Explicit backing fields are not allowed for delegated properties. + +### Smart Type Narrowing + +#### Rules + +If: + +1. the compiler can guarantee the property getter returns the same instance as the one stored in the backing field +2. the type of that instance is compatible with the property type +3. the backing field visibility allows a hypothetical direct access +4. there is no ambiguity in such an access + +it can then narrow down the returned instance type at the call site without loss of functionality. This is what is meant by the words _smart type narrowing_. + +The formal checks corresponding to the above rules are: + +1. the property does not have a custom getter, only the default one +2. `A :> B` +3. accessing: + 1. a private backing field of a class member property within the class + 2. a private backing field of a top-level property within the file + 3. an internal backing field within the module +4. property must be final, otherwise an overridden version would be able to provide a custom getter that may return a different instance + +#### Example + +```kotlin +class MyClass { + val items: List + field = mutableListOf("a", "b") + + fun registerItem(item: String) { + items.add(item) // Viewed as MutableList + } +} + +fun test() { + val it = MyClass() + it.items // Viewed as a List +} +``` + +## Alternatives + +### Initial Proposal + +Initially, the following syntax was suggested: + +```kotlin +public val items = mutableListOf() + private get(): List +``` + +> In above example `items` is `MutableList` when accessed privately inside class and read-only `List` when accessed from outside class. + +In fact, the above syntax brings in an incorrect mental model: it says _'There is a private property `it` with some *part* that declares its public behavior'_. + +Attempt to add support for the above syntax led to multiple redundant complications (see the [problems section](https://github.com/matejdro/KEEP/blob/private_public_property_types/proposals/private_public_property_types.md#questions-to-consider) + unclear override mechanics). + +## Future Enhancements + +### Direct Backing Field Access + +It might be handy to allow the direct access to the property backing field via syntax like `myProperty#field`. + +### Allow `protected field` + +This way, smart type narrowing would become possible inside subclasses as well. + +### Use of `field` Before Property Type Is Known + +Right now, any attempt to use `field` inside a property accessor in cases when the property type is not yet known (depends on the getter return value) will result into `UNRESOLVED_REFERENCE`. + +Since we now can have explicit backing field declarations, we may use them to find out the type for backing fields: + +```kotlin +var thing + field = SomeThing() + get() { + return convertSomehow(field) + } + set(value) { + field = convertTheOtherWayAround(value) + } +``` + +### Custom Getters + +Hypothetically, we could perform more analysis to find out whether smart type narrowing is possible for the given getter. \ No newline at end of file From 0c661ca31d32e567ba7f43abab94bec3de4aab45 Mon Sep 17 00:00:00 2001 From: Nikolay Lunyak Date: Thu, 16 Sep 2021 11:15:03 +0300 Subject: [PATCH 2/6] Mention backing fields mutability --- proposals/explicit-backing-fields.md | 48 ++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/proposals/explicit-backing-fields.md b/proposals/explicit-backing-fields.md index 7855f1744..702f73fa5 100644 --- a/proposals/explicit-backing-fields.md +++ b/proposals/explicit-backing-fields.md @@ -36,7 +36,7 @@ > } > ``` -With the proposed syntax in mind, the above code snipped could be rewritten as follows: +With the proposed syntax in mind, the above code snippet could be rewritten as follows: ```kotlin class MyClass { @@ -177,6 +177,10 @@ var someStrangeExample: Int If there is an explicit backing field, it must have an initializer, and the property must not declare an initializer. +#### Mutability + +For now, we assume `val` properties have immutable backing fields, and `var` properties have mutable ones. + #### Forbidden Explicit backing fields are not allowed inside interfaces or abstract properties. @@ -270,4 +274,44 @@ var thing ### Custom Getters -Hypothetically, we could perform more analysis to find out whether smart type narrowing is possible for the given getter. \ No newline at end of file +Hypothetically, we could perform more analysis to find out whether smart type narrowing is possible for the given getter. + +### Mutability + +Letting a `val` property have a mutable backing field may be useful. Consider the [following example](https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/jvm/src/kotlin/util/LazyJVM.kt#L57): + +```kotlin +private class SynchronizedLazyImpl( + initializer: () -> T, + lock: Any? = null +) : Lazy, Serializable { + private var initializer: (() -> T)? = initializer + @Volatile private var _value: Any? = UNINITIALIZED_VALUE + // final field is required to enable safe publication of constructed instance + private val lock = lock ?: this + + override val value: T + get() { + val _v1 = _value + if (_v1 !== UNINITIALIZED_VALUE) { + @Suppress("UNCHECKED_CAST") + return _v1 as T + } + + return synchronized(lock) { + val _v2 = _value + if (_v2 !== UNINITIALIZED_VALUE) { + @Suppress("UNCHECKED_CAST") (_v2 as T) + } else { + val typedValue = initializer!!() + _value = typedValue + initializer = null + typedValue + } + } + } + + ... +} +``` + From c0ebff1c1ce92ce87237c480c3cd3c73f3b88e26 Mon Sep 17 00:00:00 2001 From: Nikolay Lunyak Date: Thu, 16 Sep 2021 19:53:53 +0300 Subject: [PATCH 3/6] Paraphrase things --- proposals/explicit-backing-fields.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/proposals/explicit-backing-fields.md b/proposals/explicit-backing-fields.md index 702f73fa5..c53c9880c 100644 --- a/proposals/explicit-backing-fields.md +++ b/proposals/explicit-backing-fields.md @@ -129,17 +129,20 @@ The proposed design consists of two new ideas. ### Explicit Backing Fields ```kotlin -val it: A - [visibility] field[: B] = initializer +val it: P + [visibility] field[: F] = initializer ``` The above `field` declaration is referred to as an _explicit backing field declaration_. +Explicit backing fields is a FIR-only feature. + #### Accessors -- if `A :> B`, the compiler can provide a default getter -- if `A <: B`, the compiler can provide a default setter -- If an accessor is required, it must be provided either by the compiler or the user +- if `P :> F`, the compiler can provide a default getter +- if `P <: F`, the compiler can provide a default setter + +If the compiler can not provide a getter, the user must declare it explicitly. The same applies to setters in case of `var` properties. ```kotlin public val flow: SharedFlow @@ -175,7 +178,9 @@ var someStrangeExample: Int #### Initializer -If there is an explicit backing field, it must have an initializer, and the property must not declare an initializer. +If there is an explicit backing field, the property must not declare an initializer. + +If the explicit backing field is not `lateinit`, it must have an initializer. For `lateinit` properties initializers are forbidden. #### Mutability @@ -235,8 +240,8 @@ fun test() { Initially, the following syntax was suggested: ```kotlin -public val items = mutableListOf() - private get(): List +private val items = mutableListOf() + public get(): List ``` > In above example `items` is `MutableList` when accessed privately inside class and read-only `List` when accessed from outside class. From 0cc9fa2721e178e68f822fcfa20c8327ca41cc3b Mon Sep 17 00:00:00 2001 From: Nikolay Lunyak Date: Fri, 17 Sep 2021 20:07:43 +0300 Subject: [PATCH 4/6] Fix some things --- proposals/explicit-backing-fields.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/explicit-backing-fields.md b/proposals/explicit-backing-fields.md index c53c9880c..c0b6490c6 100644 --- a/proposals/explicit-backing-fields.md +++ b/proposals/explicit-backing-fields.md @@ -145,7 +145,7 @@ Explicit backing fields is a FIR-only feature. If the compiler can not provide a getter, the user must declare it explicitly. The same applies to setters in case of `var` properties. ```kotlin -public val flow: SharedFlow +public val flow: SharedFlow field: MutableSharedFlow? = null get() { return field ?: run { ... } @@ -254,7 +254,7 @@ Attempt to add support for the above syntax led to multiple redundant complicati ### Direct Backing Field Access -It might be handy to allow the direct access to the property backing field via syntax like `myProperty#field`. +It might be handy to allow the direct access to the property backing field via syntax like `myProperty#field` (just a hypothetical syntax). ### Allow `protected field` From 8367fb3369fec7194271aad25ffa7f92c8209206 Mon Sep 17 00:00:00 2001 From: Nikolay Lunyak Date: Fri, 17 Sep 2021 21:31:28 +0300 Subject: [PATCH 5/6] Rearrange the text and fix the indents --- proposals/explicit-backing-fields.md | 114 ++++++++++++++------------- 1 file changed, 59 insertions(+), 55 deletions(-) diff --git a/proposals/explicit-backing-fields.md b/proposals/explicit-backing-fields.md index c0b6490c6..60b7d2096 100644 --- a/proposals/explicit-backing-fields.md +++ b/proposals/explicit-backing-fields.md @@ -12,54 +12,34 @@ **Note**: initial proposal contents have been partially copied down here for convenience. Despite the different approach, the already shown use cases are still relevant. -> Common pattern in java is having full type accessible as private property and then only exposing required interface in the public getter: -> -> ```java -> class MyClass { -> // Use full type for private access -> private final ArrayList data = new ArrayList<>(); -> -> // Only expose what is needed in public getter -> public List getData() { -> return data; -> } -> } -> ``` -> -> This pattern allows easy hiding of implementation details and allows for easy clean external interfaces to the classes. But there is no idiomatic Kotlin way to achieve this pattern. Best thing you can do is define two separate properties to mimic how Java does it: -> -> ```kotlin -> class MyClass { -> private val _data = ArrayList() -> val data: List -> get() = _data -> } -> ``` +Sometimes, Kotlin programmers need to declare two properties which are conceptually the same, but one is part of a public API and another is an implementation detail. This is known as [backing properties](https://kotlinlang.org/docs/properties.html#backing-properties): + +```kotlin +class C { + private val _elementList = mutableListOf() + + val elementList: List + get() = _elementList +} +``` With the proposed syntax in mind, the above code snippet could be rewritten as follows: ```kotlin -class MyClass { - val data: List - field = ArrayList() +class C { + val elementList: List + field = mutableListOf() } ``` ## Use Cases -### Motivation - -> - There is no clean idiomatic way to do this pattern in Kotlin -> - Current best approach throws away all benefits of Kotlin properties and forces developer to write Java-like code with separate private field and public getter -> - Current best approach forces developer to assign two different names to single property (or pad private property, for example adding `_` prefix and then using this prefix everywhere in code) -> - This is another place where Java pattern could be introduced into Kotlin with less boilerplate - ### Read-Only from Outside > We often do not want our data structures to be modified from outside. Unlike Java, this can be easily achieved in Kotlin by just exposing read-only `List`. But as already ilustrated in above example, exposing different type of a property is a bit messy. ```kotlin -private val _items = mutableListOf() +internal val _items = mutableListOf() val item : List by _items ``` @@ -67,7 +47,7 @@ And the new syntax allows us to write: ```kotlin val items: List - private field = mutableListOf() + internal field = mutableListOf() ``` ### Android Architecture Components @@ -92,13 +72,45 @@ Becomes: ```kotlin val city: LiveData - field = MutableLiveData().apply { value = "" } + field = MutableLiveData().apply { value = "" } val charts: LiveData> - field = MutableLiveData().apply { value = emptyList() } + field = MutableLiveData().apply { value = emptyList() } val loading: LiveData - field = MutableLiveData().apply { value = false } + field = MutableLiveData().apply { value = false } val message: LiveData - field = MutableLiveData() + field = MutableLiveData() +``` + +### Common Java Pattern + +> Common pattern in java is having full type accessible as private property and then only exposing required interface in the public getter: +> +> ```java +> class MyClass { +> // Use full type for private access +> private final ArrayList data = new ArrayList<>(); +> +> // Only expose what is needed in public getter +> public List getData() { +> return data; +> } +> } +> ``` +> +> This pattern allows easy hiding of implementation details and allows for easy clean external interfaces to the classes. +> +> - There is no clean idiomatic way to do this pattern in Kotlin +> - Current best approach throws away all benefits of Kotlin properties and forces developer to write Java-like code with separate private field and public getter +> - Current best approach forces developer to assign two different names to single property (or pad private property, for example adding `_` prefix and then using this prefix everywhere in code) +> - This is another place where Java pattern could be introduced into Kotlin with less boilerplate + +The proposed syntax allows to achieve the same functionality while keeping the same level of simplicity: + +```kotlin +class MyClass { + val data: List + field = ArrayList() +} ``` ### RX Observable and Subjects @@ -118,7 +130,7 @@ Turns into: ```kotlin class MyClass { val dataStream: Observable - field = PublishSubject.create() + field = PublishSubject.create() } ``` @@ -130,7 +142,7 @@ The proposed design consists of two new ideas. ```kotlin val it: P - [visibility] field[: F] = initializer + [visibility] field[: F] = initializer ``` The above `field` declaration is referred to as an _explicit backing field declaration_. @@ -176,21 +188,13 @@ var someStrangeExample: Int } ``` -#### Initializer - -If there is an explicit backing field, the property must not declare an initializer. - -If the explicit backing field is not `lateinit`, it must have an initializer. For `lateinit` properties initializers are forbidden. - -#### Mutability - -For now, we assume `val` properties have immutable backing fields, and `var` properties have mutable ones. +#### Restrictions -#### Forbidden +If there is an explicit backing field, the property must not declare an initializer. If the explicit backing field is not `lateinit`, it must have an initializer. For `lateinit` properties, initializers are forbidden. -Explicit backing fields are not allowed inside interfaces or abstract properties. +For now, we assume `val` properties have immutable backing fields, and `var` properties have mutable ones. That is, assignment to `field` inside a `val` property getter results in an error. -Explicit backing fields are not allowed for delegated properties. +Explicit backing fields are not allowed inside interfaces or abstract properties, as well as they are forbidden for delegated properties. ### Smart Type Narrowing @@ -220,7 +224,7 @@ The formal checks corresponding to the above rules are: ```kotlin class MyClass { val items: List - field = mutableListOf("a", "b") + field = mutableListOf("a", "b") fun registerItem(item: String) { items.add(item) // Viewed as MutableList @@ -241,7 +245,7 @@ Initially, the following syntax was suggested: ```kotlin private val items = mutableListOf() - public get(): List + public get(): List ``` > In above example `items` is `MutableList` when accessed privately inside class and read-only `List` when accessed from outside class. From 6bdd59e5538309d65f34f298ecb9220e538f7497 Mon Sep 17 00:00:00 2001 From: Nikolay Lunyak Date: Mon, 20 Sep 2021 13:37:36 +0300 Subject: [PATCH 6/6] Replace `A :> B` with `P :> F` --- proposals/explicit-backing-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/explicit-backing-fields.md b/proposals/explicit-backing-fields.md index 60b7d2096..704f31374 100644 --- a/proposals/explicit-backing-fields.md +++ b/proposals/explicit-backing-fields.md @@ -212,7 +212,7 @@ it can then narrow down the returned instance type at the call site without loss The formal checks corresponding to the above rules are: 1. the property does not have a custom getter, only the default one -2. `A :> B` +2. `P :> F` 3. accessing: 1. a private backing field of a class member property within the class 2. a private backing field of a top-level property within the file