Allow assigning the same value to multiple targets at once

It would be nice to have this syntax:

a = b = 1

which would be equivalent to

1.let {
    a = it
    b = it
}

This doesn’t require turning assignment into an expression, but could be a special case of several = in a row.

Please state your use case. I do not see how it could be used broadly.

1 Like

This is the backdoor for:

if( obj.value = flag )

As with all proposed language changes, you have to compare the benefits against the drawbacks.

For the benefits, how often would you use this construction? How often do you need to assign the same value to several variables at the same time? I’ve converted a lot of Java code to Kotlin, so I’ve noticed every time that was done in Java — and the answer is: very rarely. No more than a handful of times in many tens of thousands of lines of code. And the current Kotlin equivalent of needing a separate line for each assignment isn’t much more complex; it takes only one extra line, and the repetition of a single value. (Or you have options such as let(), as in the question.)

So the overall benefit is tiny.

As for drawbacks, there’s the added complexity to compiler implementations, to language specs, to learning materials, and to everyone who learns the language. All tiny, in this case; but it all adds up. (Kotlin’s relative simplicity is one of its main benefits; the designers were very smart in what they chose to leave out, as well as to include.)

And there’s the risk of unintended consequences. Even if you somehow disallow it in if() and while() conditions (which is where it would become a serious liability, as @fvasco points out), I can’t see how you’d prevent its use in lambdas and other conditions. For example, it would have to allow both the new construct:

a = b = someBoolean

and the existing one:

a = b == someBoolean

— two lines which look confusingly similar, and would be easy to mistype, but have very different effects.

All in all, the score seems pretty negative (even before you subtract 100 points).

4 Likes

state your use case

Just today, I wrote this

fontMetrics?.apply {
    top = -height + oldBottom!!
    ascent = top
    descent = oldBottom
    bottom = oldBottom
}

it could have been

fontMetrics?.apply {
    top = ascent = -height + oldBottom!!
    bottom = descent = oldBottom
}

I mostly like this because this is easier to read. It might not the most often used syntax but it definitely comes up here and there, especially if you are dealing with math.

This is the backdoor for

Again, this could be a special case of several = in a row. This is how it’s implemented in Python, for instance.

the overall benefit is tiny

Apart from cleaner code, another benefit would be that this new syntax would discourage writing code like:

top = -height + oldBottom!!
ascent = top

While it might seem fine, it involves a property access, which might be slower and even produce a different result. Also, in case top is overwritten by another thread— okay, if this one is a problem you’ve got a much more serious problem at hand.

Kotlin’s relative simplicity is one of its main benefits

While I love Kotlin, I find it one of the most confusing languages, especially when looking at functions such as apply/with/also/run. Compared to these, the added “learning complexity” of same-line assignments seems very much negligible.

two lines which look confusingly similar

Good point. I don’t remember ever having a problem with this, though.

@gidds, that summary is perfect.

And I gotta say a decent example against this is the following:

I prefer the first as it appears clearer to me. The first is nicely sorted into rows while the second requires me to slow down and parse each line.

If we didn’t have destructuring and the request was instead something like val (a, b) = 1 to assign a and b to 1 then it might be better. Since we do have destructuring I think this would mostly degrade readability.

This kind of disagreement on readability is why a lot of features get stuck in the zone of subjective benefit. While some might consider it better or even equivalent in terms of readability, I find this specific request as strictly worse in terms of readability and would provide the same unmodified examples as evidence. As @gidds points out, this is when the minus 100 points is helpful.

EDIT:
If assigning multiple values is important, maybe there’s an alternative syntax? You could always make your own like this (be sure to edit and run the example :slight_smile: If you don’t like the name Val for the class you can change it right in the forum):

class Val<T>(val value: T) {
    operator fun component1() : T = value
    operator fun component2() : T = value
    operator fun component3() : T = value
    operator fun component4() : T = value
    operator fun component5() : T = value
}

fun main() {
    val (a, b, c) = Val(1) // Notice we can assign multiple variables
    
    println("a=$a, b=$b, c=$c")
}
5 Likes

In the version with chained assignment you clearly see that I’m only assigning 2 values, and also you see that top and ascent is the same and bottom and descent is the same. This is something not immediately clear from the first variant. But sure, this is a matter of preference.

If you want other examples, and if you do anything other than Kotlin, I invite you to grep your repositories for \w+ = \w+ = , I bet you will find something :wink:

I have the opposite conclusions and see it as strictly less clear, harder to see what is being assigned, and harder to see which variables have the same value.
But let’s see if we can strengthen the proposal by focusing on what problem it’s addressing–multiple assignment. Here’s the original proposed syntax:

a = b = c 
/* 
Which would be interpreted with a captured value of `c` as the following: 
a := c then b := c
not as 
a := (b := c)
and not as
b := c then a := c
This is important due to setters.
*/

An alternative that I feel fits the Kotlin style and is more explicit by way of distinguishing what is being assigned from the value would be the following:

(a, b) = c

Added parentheses and using a comma instead of equals makes it much clearer to me. No longer do I risk missing that accent is even being assigned in some top = accent = bottom = b = value, and no longer is = getting an odd double meaning. This syntax more closely fits the style of Kotlin.

If you really want a change moving towards multiple assignments, I think the most effective path forward would be to propose opening up destructuring to allow for specific edge cases. Right now destructuring has limitations to prevent abuse but lifting some would be an easier proposal than the originally proposed syntax and changes to =.

4 Likes

Something like (a, b) = c looks too much like a unpacking/parallel/destructuring assignment and would be confusing for people who program in Python, JS, Go, Perl, probably a bunch of other languages, not to mention Kotlin itself.

I agree–the syntax is basically the same destructuring. This is why I think requesting lifting the restriction on destructuring only on newly declared variables is an easier proposal.

Instead of changing the construct of = we simply say, (a, b) = c is plain old destructuring but with pre-declared a and b.

EDIT:
To clarify, this alternative proposal could be limited to not additionally add default destructuring to any object. It could simple request to enable writing (a, b) = single(c). Using single() when c is not available for destructuring or if c was already available for destructuring in order to eliminate ambiguity (for example if c was a List).

You could also propose allowing destructuring to any value to get (a, b) = 5 but since that can be done with extensions already, it might be easier to start with just one proposal at a time.

Unfortunately, it’s not that simple and there are several cons to multi assignment conceptually. For example,

  • conflict with assignment expressions (Kotlin kind of doesn’t allow this*, and as long as we use the syntax with parentheses and commas we should be okay)
  • order of assignment
  • getters (in my earlier reply I assumed a captured value to deal with this)
  • setters (imagine if a or b's setters viewed/modified each other)

These challenges are avoided with the current restriction of only allowing destructuring while declaring new variables. A proposal would need to address these issues regardless of syntax.

EDIT 2: It might be worth noting that destructuring even while declaring variables also has to deal with ordering. The order climbs up the component number so component1() then component2() and so on. You can get some odd behavior if component1() has side effects that change component2()'s returned value.
Luckily all of this is decided explicitly by the provider with how they implement their componentN(). This means that we as the consumer don’t have to worry about adding our own side effects when destructuring.

On an unrelated note, this means you could kind of detect how many components a class is being destructured into (yikes… but interesting)

I can see something like (a, b) = single(c) work but it still would be confusing. I would prefer a = c; b = a over this if only because this looks so weird, despite the problems with the latter syntax.

On the other hand, a = b = c is something that people are familiar with and will use.

The issue for destructuring assignments is 5 years old and it seems it isn’t going anywhere any time soon.

several cons to multi assignment conceptually

I’m not quite sure I understand any of these concerns

Hmm, maybe there was some miscommunication. I was under the impression that you wanted to simplify like so:

a = b = c
// Multi assignment short-hand for (what I thought we were talking about):
tmp = c // must capture c to avoid calling getter twice
a = tmp
b = tmp

This is not the same as:

a = b = c
// Assignment expression short-hand for (taken from your example of `a = c; b = a`:
a = c
b = a

And it’s not the same as:

a = b = c
// Assignment expression short-hand for (Java order of `a = b = c`):
b = c
a = b

When I suggested (a, b) = single(c) I was intending the behavior of the first example. If you play around with the runnable code and give a a setter, you will see that it does not effect b's value since c is captured and applied to both individually.

I don’t think we’ve talked about Java yet. Java does allow a = b = c. But Java does it through assignment expressions. Java also does not have property variables, it only has fields, meaning no getters or setters are involved.

For Kotlin, a = b = c as used to mean, a := c; b := c could be confusing since users may be familiar with Java’s meaning of a := (b := c), or more verbosely said as, (a := (b := c; return c); return a).
If we assume we want assignment expressions to match Java, then all we have to deal with is the difficulty of getters and setters–the short-hand would be:

a = b = c
// Assignment expression on properties short-hand for (using Java / right-hand ordering):
a.set(b.set(c).let { b })

Or for left-hand order (from your example of a = c; b = a)

a = b = c
// Assignment expression on properties short-hand for (using Java / right-hand ordering):
b.set(a.set(c).let { a })

Am I missing anything?

EDIT:
Instead of a one-to-many assignment, a one-to-one multi assignment concept might be nice. It’s not what was requested by OP but as mentioned in the issue link, something like “x, y = y, x + y” and in our example, “a, b = c, c” might be a stronger proposal as it avoids the need for all of the assumptions with captured values and ordering.

2 Likes

Oh, this is very nice counterargument that I haven’t considered. Indeed, coming from Java, it would be reasonable to assume that in a = b = c b is assigned first. This might be not be too big of a problem per se, but given the other issues and the minus 100 points this definitely sinks the idea below zero. I guess I’m resting my case, then.

Perhaps this suggestion can be reconsidered when Java finally fades into oblivion.

Thanks for great input, @arocnies!