Description
The meaning of :
for significant indentation is more contentious than other aspects. We should come up with a crisp and intuitive definition where :
is allowed.
One way I'd like to frame significant indentation in Scala 3, is that braces are optional, analogously to how semicolons are optional. But what does that mean? If we disregard :
it means:
At all points where code in braces
{...}
is allowed, and some subordinate code is required, an indented code block is treated as if it was in braces.
The second condition is important, since otherwise any deviation from straight indentation would be significant, which would be dangerous and a strain on the eyes. But with that condition it's straightforward: If some subordinate code is required, either that code follows on the same line, or it is on a following line, in which case it should be indented. Braces are then optional, they are not needed to decide code structure. In the following I assume this part as given.
So that leaves the places where some code is not required but we still like to insert braces. There are two reasonable use cases for this:
- Between the extends clause of a class, object, or similar and its (optional) template definitions in braces.
- In a partial application such as
The motivation for this second case is to make uses of library-defined operations close to
xs.foreach: ...
native syntax. If native syntax allows to drop braces, there should be a way for library-defined
syntax to do the same.
Possible schemes
The current scheme relies on colons at end of lines that can be inserted in a large number of situations. There are several possible alternative approaches I can see:
-
Don't do it. Insist on braces for (1) and (2).
-
Split (1) and (2). Make indentation significant after class, object, etc headers without requiring a semicolon. This has the problem that it is not immediately clear whether we define an empty template or not. E.g. in
object X extends Z // ... // ... object Y
it is hard to see whether the second
object
is on the same level as the first or subordinate. But semantically it makes a big difference. So a system like that would be fragile. By contrast, a mandatory:
would make it clear. Then the version above would be two objects on the same level and to get a subordinate member object Y you'd write instead:object X extends Z: // ... // ... object Y
So I actually quite like the colon in this role.
-
Split (1) and (2) but require another keyword to start template definitions after class, object, etc headers. @eed3si9n suggested
where
. It's a possibility, but again I do like:
at this point. It reads better IMO (andwhere
might be a useful keyword to have elsewhere, e.g. in a future Scala with predicate refinement types). -
Keep
:
(or whatever) to start templates but introduce a general "parens-killing" operator such as$
in Haskell with a mandatory RHS argument. If that occurred at the end of a line, braces would be optional according to our ground rules.I fear that a general operator like that would lead to code that was hard to read for non-experts. I personally find
$
did a lot more harm than good to the readability of Haskell code. -
Restrict the role of
:
to the two use cases above. I.e. allow a colon if- a template body is expected, or
- in a partial application, after an identifier, closing bracket
]
or closing parenthesis)
.
This way, we would avoid the confusing line noise that can happen if we allow
:
more freely.
My personal tendency would be to go for that last option.