-
Notifications
You must be signed in to change notification settings - Fork 367
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
A proposal for the assign operator overload #309
Comments
This KEEP looks like a rewriting of the delegation, so having two features to solve the same tasks can lead to confusion. My main unanswered question is: is this solution clearly better than delegation? Does this code have a meaning, or is it an abuse? val myOptional = MyOptional()
myOptional = null
if (myOptional == null) println("myOptional is empty") Should this code compile? val a = StringProperty() // unmodifiable variable
a = 3 // a.assing(3) and this not? var a = StringProperty() // modifiable variable
a = 3 // error It is unclear to me if there is an interference between this KEEP and (Mutable)Collection. Should this code compile? val a = listOf(StringProperty())
a[0] = "a" // a.get(0).assign("a") or this? val a = mutableListOf(StringProperty())
a[0] = "a" // a.set(0, "a") |
Were union types considered as a possible solution? i.e.: open class MyTask {
var input: Property<File> = Property<File>()
set(value: Property<File> | File | Path) {
field = when (value) {
is File -> Property(value)
is Path -> Property(value.toFile())
is Property<*> -> value
}
}
}
} (assuming setters were allowed to receive parameters with types different than the field type) Of course this would be a much bigger effort, but that's a feature that solves lots of use-cases. |
Thanks for your interest in that feature. I will try to answer some questions.
Delegation was considered, it's discussed here. In short, the main problem with delegation is, that you can't use
They were considered. Unfortunately, this is a really big feature and as I understand it's still far away. It's also somehow related to that topic discussed here. So in short: it's possible, it could solve many issues, but it requires a lot of effort and it needs a lot of design since it affects all parts of the language.
This can have some meaning, but statement
This compiles if you have
This doesn't,
This would work the same as now, so it doesn't compile since
This would work the same as now for the same reason mentioned above. |
Thank you @asodja for pointing to documentation, I already seen that.
If I understand well, then the goal should be to reduce "boilerplate", i.e.: input2 bind calculatedProperty
input2 = calculatedProperty In my mind, these lines of code do different operations, so it is not easy for me to understand why both should use the same syntax. In the case: val input: Property<Property<*>>
val calculatedProperty: Property<Property<*>> The result of the statement: input = calculatedProperty is a puzzle for me.
Yes, I supposed that, but my question was: Does this code have a meaning, or is it an abuse?
I agree, this KEEP makes a switch from
This doesn't looks so much POLA for me. I feel that this feature is too difficult to teach, the meaning of Similarly, Scala proposed the Implicit Conversion feature, more coherent to the language design. I am not suggesting to implement that, it shares some issues with this proposal without adding significant advantages. |
@asodja this part ( |
What if... just like with definitely non-nullable types, a limited version of union types was implemented? I'd much prefer something that's limited but can be expanded consistently in the future instead of a low-effort solution that will introduce more implicit behavior and be redundant once the language evolves to have things such as union types or setter overloads. Also, implicitness is not the only problem here. The examples given by @fvasco show how inconsistent some things would look. It gives me chills to think it would be possible to reassign |
Some questions:
operator fun Any?.assign(other: Any?) {}
fun main() {
"x" = 1 // Will this compile?
}
operator fun Int.assign(other: Int) = true
fun main() {
if (1 = 1) println("Wat")
}
build.gradle.kts// assume isDevEnv is nullable and thus we cannot simply do `if (application.isDevEnv)`
if (application.isDevEnv = true) { // oops, meant to be `==` but it compiles
doSomeAdditionalStuff()
} |
@edrd-f, the proposed operator overloading depends on the declaration site, so both I suppose that the overload is applied for operator fun Any?.assign(other: Any?) {}
const val STRING = "string"
fun main() {
STRING = "strange"
} Function's parameters are not covered, so this code should not work: fun foo(property: StringProperty) {
property = 3
}
It looks no, the signature of Java class is |
IMO, This proposal adds complexities and causes many problems :)) |
The way I see it, this adds more complexities and problems than the benefit gained. The only advantage mentioned that this proposal has over overloaded getters and/or union types is that the latter are large features and are harder to implement. I don't think that a quick-and-dirty patch adding a rather niche feature with a lot of complexity and implicit confusion is a good solution, especially when the alternatives are major features that are requested by large parts of the language's user base. Besides, if someone really wants this feature, they can very easily hack together something nearly identical in the current language state/featureset: infix fun Int.`=`(i: Int) {
println(this + i)
}
fun main () {
1 `=` 2
// prints 3
} (tested in the Kotlin playground with Kotlin 1.7.10) I don't see a reason to add this feature: it's easy to abuse, adds a lot of confusion and implicitness, and those who desperately want it can implement something nearly identical themselves. I don't see a reason to add it to the language itself. |
It seems that only (?) major use case is the Gradle scripts' DSL. Is that the only motivation here? Wouldn't be possible to implement such "extensions" conditionally as a compiler plugin - similar to all open, etc.? Assign overloads could also work only on predefined/anotated types, etc. |
In such case the effect of assignment depends on the declaration site, the |
Perhaps a |
From the comments, I believe that one of the concerns is that this feature will change the meaning of ̇With that in mind, it makes no sense to implement a custom And of course, abuse is always possible. But as you normally wouldn't use operator overload unless for some specialized types, my argument is that this feature won't be normally used in user code unless for some specialized type and due to that it won't change the meaning of the
While writing normal Kotlin app code, your view is probably the one to follow. But for the DSL I am not so sure it is always desired. task.apply {
// input file is: val inputFile: Property<File>
inputFile.set(File("some/file"))
inputFile.set(otherTask.flatMap { it.outputFile }) // otherTask.flatMap { it.outputFile } returns Property<File>
} And this reads just: set With assign operator feature, it could be just simply (without annoying .set and ()): task.apply {
inputFile = File("some/file")
inputFile = otherTask.flatMap { it.outputFile }
} Currently best what we can do without task.apply {
inputFile.value = File("some/file")
inputFile bind otherTask.flatMap { it.outputFile }
} So it's very inconsistent depending on the type you assign and it makes DSL more complicated with a more cognitive load. |
Valid concern. I believe that this feature won't be redundant after/if the union type or setter overloads land. I see that there is some overlap in some cases but they probably solve different problems. For example, if you look at:
Now, this makes sense for that small example and when you are the owner of MyTask type. But as a "library provider" I don't want that every user of my library would have to reinvent such setter for every field of type But I can always be wrong here since I don't know in detail how union types would work and if you could solve a particular issue with some limited implementation of union types or not. Regarding the implementation, the Now if you want to try what would be possible you can take |
I realized that this KEEP clashes with my mental model. A similar feature is Kotlin delegation is similar, it declares a different variable handling at the declaration site. |
Actually, a year ago this exact example (Int assignment to String) was suggested by a community member: (https://kotlinlang.slack.com/archives/C0B9K7EP2/p1610457810240300) So it's not an extreme example and it shows yet another form of abuse this proposal allows.
Makes sense, however this use-case looks so specific I'm not sure the reduction in code duplication outweights the downsides. |
This proposal goes too far on the operator road that leads to such confusion as mentioned. In my opinion the advantage is way smaller than the disadvantage, as it just saves something that doesn't require much effort by the use of delegation. It's only reasonable in DSL, but is provided to be used globally. interface Container {
val property: Property<String>
}
val container = stub()
var property by container.property
property = "Hello"
|
Update: we (Gradle build tool) had a discussion with the Kotlin team some time ago due to concerns from the community. It was later decided that assign operator overload won't be a language feature. But we will implement a Kotlin compiler plugin that will allow similar behavior (note: it was not possible to implement such a plugin at the time the proposal was written). I think that resolves all concerns and also works great for the Kotlin Gradle DSL. Thanks to the Kotlin team for finding a solution and to the Kotlin community for raising valid concerns. Note: since I am not a member of the Kotlin team, I will leave the final official resolution of this issue to them. |
That's awesome. Thanks @asodja, Gradle team and Kotlin team for listening to the community. ❤️ |
@asodja this compiler plugin idea sounds awesome. Is there anywhere we can go to see progress on this plugin or to test it out? I am curious how similar it would be to the original proposal. I have a couple of questions:
My motivation for caring about this right now is to see if I can make I like to make classes where all properties are lazy. The issue is, I dislike the way this looks in my code. I think it becomes tedious to read and write code like this. For example, here is a non-lazy class. It's perfectly concise.
Now, here is the lazy version:
In my opinion, repeating the If I were only using What I would love is some sort of custom delegation operator that is a shortcut for
Does anyone else feel similarly that a feature like this is missing? Or is there some alternative solution that I am missing? |
mgroth0 The compiler plugin does not allow adding new operators (like |
Since we've rejected the idea of adding the corresponding convention, as described in the KEEP, to the language directly, but are providing an extension point that allows the similar implementation as a compiler plugin to be used in Gradle, I'm closing this issue and PR. The documentation for the feature shall change (as only a specific Gradle DSL behavior needs to be documented) and shall be hosted elsewhere. The documentation of the extension points itself will be performed in the future during compiler API stabilization. |
This KEEP is a proposal on providing an assign operator overload that would provide DSL for assigning values to mutable container objects. For a full text of proposal see assign-operator-overload.md in the corresponding PR. Please, use this issue for discussion.
The text was updated successfully, but these errors were encountered: