Apologies in advance if I’m missing something obvious and simple that refutes this post, but I’ve been sitting on this for a while and asked a few other users with no good answers.
I teach a beginner computer science course using Scala.
- We use scala3 fewer-braces whitespace notation.
- Students are instructed to put spaces before type ascriptions, to unlock columnar vertical alignment across lines and to more closely resemble the type theory literature covered in the course. e.g. we write
val x : Intinstead ofval x: Int.
These two at play have revealed a stumbling point in the syntax for beginners: students repeatedly act as if scope-opening colons are the same sort of thing as type ascriptions, by writing
// ↓ extra space, incorrect
class Foo :
val x : Int
↑ extra space, correct
and
// ↓ extra space, incorrect
xs.map : x =>
It’s not simply a muscle memory issue, students tell me that they don’t understand why they are supposed to put a space before colons in one scenario but not the other.
Further, students also struggle with understanding why : is not required for extension and match and for etc. Student frequently assume they do need to write : in these scenarios
// ↓ incorrect
extension (x : Int):
def ...
.. ↓ incorrect
x match:
case ...
// ↓ incorrect
for x <- xs do:
...
That : is overloaded to have multiple, dissimilar meanings in the language, makes it harder to learn, and feel less coherent in design (as in, had Scala aimed to be a whitespace language from the very beginning, I imagine : would not be used in all of these ways. It feels very retrospectively duct-taped on).
I understand that there is probably no fixing the use of : in lambdas at this point, but this has gotten me thinking, what purpose does : even serve in class/object/enum/trait declarations? You already are required by the compiler to put a new line and indent afterwards, it’s not as if users are allowed to write single-line declarations like class Foo: def f = 0 which would actually make : a useful delimiter. The token seems to be entirely redundant, except I suppose for the case of self-types? Are there any other cases?
I would like to propose : being optional in these declarations, with newline+indent as the sufficient delimiter, enabling the following
trait Foo
val x : Int
trait Bar extends Foo
val x = 0
class Baz(
val x : Int
)
def f = 0
enum Color
case Red, Green, Blue
It is in fact what most of my students already write most of the time, before I annoyingly remind them that they forgot the colon. (which is evidence that newline+indent alone is already enough for learners (and I know I personally find the colon to only contribute ugly visual noise)).
I don’t believe this change would break pre-existing codebases as : would only become optional, not illegal. Very analogous to how before 3.8, \n was required for higher order functions, but now they are optional and single line lambdas are allowed.
But I may be missing something. If anyone can think of cases where this change would be problematic for the compiler or keeping pre-existing code bases legal, please comment below.
(I would also like to pre-empt that even if one dislikes the scala3 whitespace notation and wishes we would avoid all of this with braces, we should agree that since we have already decided to seriously support it, we should at least make the experience feel more coherent for those who want to use it.)