There is a surprisingly simple solution to all the tuple syntax trouble.
But I don’t know whether you’ll like it. Despite it’s coming even with a small quality of live improvement.
The idea is:
One could simply disallow parenthesis for grouping.
Braces would become mandatory for grouping.
Consequently one would end up with only one canonical Unit
literal, namely {}
, as “grouping nothing-really into an expression” results in exactly this expression, an empty expression, which is by definition of type Unit
.
Using braces for grouping, and using {}
as Unit literal works already as of today.
One would just need to rewrite syntax; which can happen fully automatic as the compiler recognizes Unit
literals and grouping parens.
Doing that would than free the parens syntax exclusively for Tuple
literals.
Than ()
is definitely an empty tuple. (())
would be a Tuple(Tuple())
.
One would have also literal syntax for things like
Tuple(Tuple(Tuple({})))
for which the compiler currently outputs as type
((Unit *: EmptyTuple) *: EmptyTuple) *: EmptyTuple
instead of simply
(((Unit)))
(Don’t ask me whether such a type makes sense, but it could be written with such syntax.)
Literal syntax could exactly match type syntax also for zero and one element tuples.
(One could maybe even use the “new” Unit
literal in type syntax? But not sure about that as this would end up in making Unit
redundant. Maybe it would be good to also get rid of it, but maybe that’s too much of a change. Doing that would potentially loose a pronounceable name for this entity, which is not good.)
I’m aware that the idea to disallow parenthesis for grouping is bold. This is not only a diversion from what other programming language do but also from common math notation. Even people new to programming have some expectation that a computer can do math and understands math notation, as a computer is “a big calculator”.
Not being able to write a familiar
(21 + (1 + 2) * (3 + 4)): 42
expression, but being forced to write
{21 + {1 + 2} * {3 + 4}}: 42
is indeed questionable.
But math syntax is heavily overloaded. The expressions aren’t typed, so it makes no difference there.
Programming languages have different requirements. Writing code is not doing math!
Also the change comes with some ergonomic improvements:
Imagine you need to debug the above expression. You want to know what the first inner grouped expression (1 + 2
) evaluates to.
When using braces you can simply place the cursor before the 1
, press enter, and write some debugging code like
{21 + {
val r = 1 + 2
println(r)
r} * {3 + 4}}//: 42
(We need to comment out the result type as the compiler is “to stupid” to remember that 1 + 2
is 3
and not only some arbitrary Int
; but that’s a different story.)
Had you instead used parens for grouping you would need to first change them to braces, just to start debugging… That’s annoying! That’s bad for the same reason for why it was bad in Scala 2 to need to add braces when extending an one line method to a multi-line method, which was common for exactly such println
debugging. That’s thankfully not necessary any more with the indentation syntax.
Of course “println
debugging” here is just a placeholder for arbitrary refactorings.
To summarize:
The result of this move here would be super clean tuple syntax for all tuple arities, and no confusion with Unit
literal syntax.
Using braces for grouping is actually more ergonomic than using parens, even it looks “a little bit funny” at first.
After thinking about it I came to the conclusion that {}
as Unit
literal is more consequential than a syntax that looks like an empty tuple. “Effectively nothing-really” is simply not a tuple—and you really don’t want to pass Unit
by accident to a function that takes a tuple, which is almost certainly a bug. But nothing-really “grouped” into an expressions makes very much sens as Unit
literal.
Oh, and one more thing: This would remove one case of “you can do that in different ways in Scala”. Currently you can use ()
and {}
to group expressions, and when empty as Unit
literal (to be honest, the later to my surprise). But why do we need so much flexibility in writing nothing-really? Why do we need to explain the difference between ()
and {}
for grouping? Actually for no reason as braces always work (in contrast to parens).
No of the proposed syntax changes introduces new syntax (besides introducing the missing Tuple0
and Tuple1
literals of course). At the same time it would streamline and align the current syntax options a lot. That’s simpler to teach, and simpler to remember.
The only change of the status quo would be that now you’re forced to use grouping braces instead of just having the option to do so. Less syntax options, less choice paralysis. Less arguing about “style”… That’s very beginner friendly!
And for the friends of esthetics and symmetry:
((( {} ))) == ( {} ) // Compile error!
// The symbol pattern looks different, so
// how can it be the same?
// You simply can't compare
// `Tuple(Tuple(Tuple({}))) == Tuple({})`
// Makes no sense.