Recently a beginner on reddit asked a question about why a += if (true) 1 else 2 didn’t work. We know it’s because that desugars to a = a + if (true) 1 else 2, and if isn’t a simple expression therefore it isn’t a valid RHS for +.
But it got me thinking, why don’t we desugar into x += y into x = x.+(y) instead? This means that y no longer needs to be a simple expression.
I think this disproportionately affects new users since they tend to be both using var and not knowing how to solve this issue.
So I propose that desugaring += would wrap the RHS in parens if there are no parens already there. I believe this should be backwards compatible with existing code.
I think this is my biggest problem with it personally. New users don’t know this, and don’t have a clear way to find out.
I’d refine my proposal to say “any method that is sugared to look like an assignment operator should convert the RHS to a simple expression before passing it as a parameter to the method”. x_= is a good example showing the compiler can and does do this.
I run into this all the time in sbt builds, where I’m constantly wanting to write a := if ... or a ++= if ... but can’t without parens or braces. And := and ++= aren’t syntax, they are just method calls. I doubt this is worth addressing unless the fix works for infix method calls in general.
So for example, could a + if (true) 1 else 2 be made legal? And if not, why not?
Do we have to change the language or just improve (special-case) the error message? If the error message said what to do, that would solve what Nathaniel said that users don’t have a clear way to find out what to do.
But that expression is okay (presuming that it is well defined).
Most languages allow expressions with a mix of operators without requiring brackets even if it would be hard for a human to easily see the precedence. The expectation that the author should put brackets in when it is not obvious.
Forcing brackets around these cases often seems a bit ugly in Scala.
I agree with OP and Seth, it’s very surprising behaviour, and I run into it frequently; it’s annoying having to go back and putting the parentheses around the if expression. I don’t know what is technically the best solution, but from user perspective it’s not optimal currently.
That desugaring happens after parsing but the parser is the one which rejects that expression. It seems that the parser could be changed to simply accept this without changing anything to the desugaring, for example the following already works as I expect:
scala> var x = 2
var x: Int = 2
scala> x *= 1 + 1
val res1: Int = 4
So x *= 1 + 1 is (thankfully) equivalent to x = x * (1 + 1) and not x = x * 1 + 1.
They could not, not sensibly, because the precedence of + is higher.
But the precedence of = matches the precedence of +=.
We don’t have anything to generalize except that = is a special snowflake with its own precedence class, and it shouldn’t be. = might just be a call to update…it might be a method too. So it’s really no different.
scala> val m = collection.mutable.TreeMap.empty[String, String]
m: scala.collection.mutable.TreeMap[String,String] = TreeMap()
scala> m("fish") = if (true) "salmon" else "herring"