-
Notifications
You must be signed in to change notification settings - Fork 371
Add support for explicit backing fields #278
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
Comments
Interesting read.
How would this be catered for? |
I have one concern: Has the possibility of allowing it been considered, despite the fact that it'd require more than one actual backing field? |
As of now, we assume Thanks for your example, I've added it to the enhancements section. |
If you call val flow: StateFlow<T>
field = createSomeMutableStateFlow()
get() = field.asStateFlow() Then the smart type narrowing won't work, since it only knows how to deal with the default getters. |
I think it's not a big deal in terms of price, it's just a small object without any actual state, only with 1 reference |
How doable would it be to address this use case in the design of this feature, or at least, make some room for it for future evolution? |
At least on the jvm that is not valid. "final" and "immutable" are different concepts although they are mostly equivalent. More important is that from a consumer perspective (rather than class implementor/compiler) the assumption cannot be made as the backing field (or lack thereof) cannot be assumed - changing it is API and ABI compatible. |
I'm much more after the original proposal, because (from my understanding):
val flow: StateFlow<T>
field = createSomeMutableStateFlow()
get() = field.asStateFlow()
You write there are complications with the original proposal - well, my answer to both linked questions are yes and yes, but this could be considered incrementally, just as here you start only with private and internal fields. So I'd like to iterate on why the former proposal cannot be implemented, I don't see the why now. |
Hypothetically, it's possible to introduce a syntax like |
Thanks for your correction. I simply meant that assignment to val test: Int = 1
get() {
field = 2
return field
} And right now it works the same for properties with an explicit backing field as well |
It seems to me, sometimes consumers need more control over the stored value, which brings in backing properties. Feels like introducing a |
I suppose, analyzing getter return value could still make them usable without loosing the STN. It's true that the STN may not be able to solve all the problems, but it still solves the base problem that gave birth to this discussion. Please, keep in mind that "Delegated property cannot have accessors with non-default implementations". The original proposal doesn't mention anything about delegated properties. From what I see, there's nothing preventing the support for setters overloading in the current proposal. I think it's a bit different topic, not "a direct enhancement". |
The biggest reason why personally I prefer the new approach is the fact that it's really easy to understand. It builds around the already known idea - a backing field - which is just an implementation detail, and this information may be further used on the implementation side to narrow down the type. The original proposal doesn't mention how exactly the new getters (the ones with a more permissive visibility) would work. Would they work via the same mechanism as the STN? Then the explicit backing fields syntax seems less cryptic to me. Would they not get called when accessing from the scope relevant to the visibility of the property itself so that we have direct access to the backing field? Then we break the rule "getters are always called". How would they interact with usual getters? Prioritization rules of mixed getters seem unintuitive. The concept of new "exposing" getters introduces such questions as "can we both override the property and its exposing getter" (while implementing the original proposal, the ability to perform such overrides was added). The use with delegates was also unclear. |
Mostly yes, but it's better to solve more in one step than less, if only because it should end up being less complicated.
Yes, it also doesn't mention their not supported :). And, it looks to me like they should easily be possible. Right now you can do: private val _foo by lazy { mutableListOf("cOmPLeX") }
val foo: List<String> get() = _foo So it could become: val foo by lazy { mutableListOf("cOmPLeX") }
public get(): List<String> Which would not be possible with explicit fields.
Yes, I mean that it would be nicely symmetric to have multiple getters and multiple setters.
So for me at least, this is the other way around. I knew at a glimpse what the original syntax was about (maybe because I already knew of the proposal), and I actually pondered quite a bit on what's going on with the new one:
So to clarify: my view is that there is ever only one actual getter with body, the other ones are just declarations that say more about it's type (and therefore probably should not have parentheses as proposed), the same way as However, I do see an unmentioned one: val foo: List<String>
private get: MutableList<String> // <- Property is visible outside under type List<MutableList>
val bar: List<String>
private get // <- Property is not visible outside These too very closely looking syntaxes have quite a different meaning and so are not intuitive. This is not much of a problem right now because It's not to say it is perfect either, you may e.g. want to change the order of visibility modifiers so it becomes: private val foo: MutableList<String>
public get: List<String> Which would read as "There is a |
I'd like to add my 5 cents.
IMO it's not a question of "cannot", of course it can be implemented. It's a question of how clear the result is. Let's take even the basic example
Let's try to answer:
and now it's quite clear that With possible overrides, situation is even more interesting:
Note that code above isn't valid despite of the fact it's Ok for first glance (type of
|
About delegates, I'd not say that the code like
cannot be written in new syntax at all. Well, it's true that it's not supported currently but in future we could have something like (just a hypothetical syntax)
or, may be (longer but a bit clearer)
|
The type is val foo = ...
public get: List<String>
private get: MutableList<String> This may look cleaner (also I use just
This sounds clear - you have a Now to the inheritance:Your first example of course wouldn't work - it wouldn't even now, you can't narrow down the type of overloaded property. So yes, interface Base {
val foo: List<String>
}
class Derived : Base {
override private val foo: MutableList<String> = ...
public get: List<String>
// But it could be also written as:
override public val foo: List<String> = ...
private get: MutableList<String>
// Or maybe like that? It should be decided which ways are allowed and which are not to make it the most clean.
override val foo = ...
public get: List<String>
private get: MutableList<String>
} The rule is simple, the interface only declares a public type and the public type of overridee cannot be narrower than it - exactly like presently. Then the private type cannot be narrower than the public one; this then expands to other visibilities.
No, it's still a property. At least at the backend there is a getter method About delegates:
Probably won't work - field cannot be implemented by delegate, only property can.
The type of But even if one of these, or some new syntax would work, the delegate instance would still need to be in the backing field, probably yes, this is often the case, but not always. And then it still doesn't support properties without a backing field. |
UPDATE: There has been a massive update to the proposal text in PR #289 that addresses many of the issues raised here. Please, see explicit-backing-fields.md for the full text:
|
I happened to stumble across this branch which seems to implement a syntax for Explicit Backing Filed Access in a form of: var number: String
field: Int = 0
get() = field.toString()
set(value) { field = value.toInt() }
fun updateNumber() {
number#field += 100
} This reminded me I was about to suggest mine, which is to, instead of adding language syntax, add a fun updateNumber() {
::number.field += 100
}
|
Hello, @mcpiroman! The direct field access syntax is still TBD, and this branch is a sandbox where I'm looking for possible problems we may face if we decide to implement the feature in the form of The idea of having an additional type parameter for |
Yeah, I just used the syntax from the branch to compare it to KProperty approach. I see the The problem though would be in such a case: class Foo {
val number: String
field: Int = 0
get() = field.toString()
fun exposeNumberProp() = ::number
}
fun main() {
val n = Foo().exposeNumberProp().field
| the backing field is meant to be class-private in ABI (at least on JVM), so it wouldn't work. So either the field would need to be public (but inaccessible for java) or some additional tracking rules would need to be applied - quite possibly a simple one, but still. |
val names: MutableList<String>= mutableListOf()
get(): List<String> I like this syntax, it makes more sense to me at least, developers need only set a return type on the getters using an already familiar syntax. |
That was the syntax in the original proposal. We've prototyped it and ran into multiple problems with
|
According to the issue https://youtrack.jetbrains.com/issue/KT-14663 this feature has a target version of 1.7.0 and is marked as fixed, but based on the list on https://kotlinlang.org/docs/whatsnew17.html#top it's not available in 1.7.0 yet? Is there any public information available on what version of Kotlin this might hopefully be featured in? |
1.) it requires explicit enabling of K2 compiler 2.) it requires explicit opt-in as experimental feature However, it can be made to work https://gist.github.com/dellisd/a1e2ae1a7e6b61590bef4b2542a555a0 |
Prototype of this proposal has been delivered in Kotlin 1.7.0 as a part of K2 compiler preview. In order to try out his new feature you need to enable the K2 compiler with |
@thumannw It's not only shorter, but the biggest advantage is naming (_no _more _this) and improved code locality. And one can argue that setter only property workaround is to use method instead of property |
Hello from kotlin conf 2024! This matter was just discussed in a talk by Michail. I was wondering if this proposal also involves setter overloading: private var user: String
set(user: String) { field = user)
set(user: User) { field = user.username } This would be very useful for DSL building. Also, another use case would be a private field which needs to be nullable, but the setter should not accept null as an input. In Java, setters are just normal methods and therefore overloading works out of the box. It would be really nice to have the same experience with kotlin properties. |
https://youtu.be/tAGJ5zJXJ7w?t=1860 |
Hi @MartinHaeusler. No, this is actually a different feature. We're aware of the use-case with DSL, but there are currently no plans in this regard. Here is an issue to follow for further updates: KT-4075. |
Would be nice to have an opportunity to set modifiers to the explicit backing field (at least protected) open class C {
val elementList: List<Element>
protected field = mutableListOf()
} to have access to this field through inheritance class D : C() {
fun reset() {
elementList.clear()
}
} Use caseabstract class MviModel<State, Intent, Action> (
initialState: State,
): ViewModel() {
val state: StateFlow<State>
protected field = MutableStateFlow(initialState)
val actions: Flow<Action>
protected field = MutableActionsFlow()
abstract suspend fun process(intent: Intent)
// ...
} class SampleMviModel(): MviModel<State, Intent, Action>(
initialState = State.DEFAULT,
) {
override suspend fun process(intent: Intent) {
state.update { /* TODO */ }
actions.send(Action.Error("oops"))
}
} state and actions will be immutable outside and mutable inside |
@Railian this is exactly what I need in my project as well. Unfortunately it seems that this feature gets 0 attention from JetBrains despite being announced at KotlinConf |
Uh oh!
There was an error while loading. Please reload this page.
Please, see explicit-backing-fields.md for the full text in PR #289.
Summary
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:
With the proposed syntax in mind, the above code snippet could be rewritten as follows:
The text was updated successfully, but these errors were encountered: