Proposal/Discssion for more consistent indentation based syntax

The TLDR of this is:
In its current form the indentation based syntax seems a bit inconsistent in how it is handled in scala3 (to me as a beginner of scala3/only maintain a few scala 3 industry products, but 10yrs in scala2). Let’s see if that can be improved!

The long story:

In scala3 I have to remember many different ways of opening a scope, while in scala2 there was one way you always used, braces. Here are some examples of what I need to remember additionally in scala3 for starting new kinds of scopes - When to use:

  • then (if expressions)
  • do (for expressions)
  • with (givens)
  • : (traits, classes, objects, etc)
  • = (also present in scala2 obv, but could always followed by brace)
  • (extensions, match expressions)

How can we make the braceless syntax just as consistent? This post/thread is about seeing if there are ideas to that. Maybe there are already plans in motion to address this?

I get the sense that the indentation based syntax implementation still is kind of experimental, and there should be a much more consistent way of handling it - something that prob could be fixed without breaking backwards compatibility. Right now I am starting to get the old bash shell scripting feelings when I consistently have to go google examples for writing simple control structures.

This said, I have no experience with language implementation and I dont know how complicated it could be to solve - BUT imo it would be hugely positive if we could improve this by picking a way to harmonize the different “begin scope”.

For Example: Would it be possible to make it so : worked in absolutely all the situations above? Then I wouldn’t have to remember any of the above. It doesn’t have to be : necessarily, it could also be some other keyword/symbol/something/begin. Or maybe, unless you opt in to write a single line scope, a new line itself is enough, and we don’t even need any magic symbols at all? (as is currently the case for match expressions and extensions for example)

Thoughts?
Would like to write more scala3 without braces but the above is confusing to me :smiley:

To make it absolutely clear: This is not an anti braceless/anti indentation post. This is about trying to improve the current indentation based syntax.

7 Likes

The braceless syntax also took me a while to get used to but now I just love it. But a Scala-bonus is that they are optional so you can always go back to your braces whenever you like.

There has been so much care, experimentation and discussion put into this, including this epic thread with almost 600 posts (sic!): Feedback sought: Optional Braces - #588 by odersky
and then there is also the discussion on fewer braces (where colon i king :slight_smile: )

So I recommend to continue experimenting for a big while and perhaps after that while there is a god chance that you do find it very intuitive – and in cases where you want the brace-explicit syntax you can just use that. The braceful syntax is promised to never go away…

It may. But I’d rather not keep writing braceful code when the world is shifting :slight_smile:. In all honesty I would personally prefer to enforce removal of braces :smiley: . Not because I have enough knowledge to say it is objectively the better design, but because I believe the language would be less concerned with debate and more concerned with productive evolution. (but that’s something I would rather not debate in this thread)

But: What is my concern is why there are so many different ways to open scopes. It should be possible to stay braceless but still be consistent with scope openings.

Example: Why do we have both with and : - shouldn’t one of them be enough? maybe there is a clear reason? And why are there situations where we don’t have any opening at all? (such as extensions and match ). I think it could make sense to make the language more consistent, and still staying braceless

3 Likes

In the cases you point out there are rationale behind it and I think you can find it in the epic thread, e.g. why no with or colon after extension? because it’s not a template body where you can have fields etc, see e.g. around here in the epic thread: Feedback sought: Optional Braces - #391 by odersky

Also there are reasons for having with after given values that fill in abstract members - I think it’s in the epic thread but could not find it right now. If I remember correctly it is because there would be too many colons hurting your eyes…

You can also find some of the rationale for the variation in how blocks can be opened here: Optional Braces

The principle of optional braces is that any keyword that can be followed by { can also be followed by an indented block, without needing an intervening :. That’s in practice a simple principle to internalize, even though it does require some unlearning when you come from Python.

Allowing an optional : would be counter productive since it would introduce several ways to do the same thing.

1 Like

Thanks for the answer. Is there any document available with examples? I have a hard time understanding what this means in practice. For example

trait A {
  def foo..
}

trait B: 
  def foo..

what here is the keyword that can be followed by {? is it trait?

any keyword that can be followed by { can also be followed by an indented block, without needing an intervening :

But above we must write : in the new syntax, it seems to break the rule, so I am confused. However

extension (x: Int) {
  def foo..
}

extension (x: Int)
  def foo..

Here extension (x: Int) can be followed by {, so in this case it seems to follow the rule, and we don’t need to add a :


update/wrong: In the case of givens (below) it seems to also break the rule?

given Foo[Int] with 
  def bar..

given Foo[Int] { // wrong
  def bar..
}

I’ll try to read up in the large thread what discussions have already been had, unfortunately I didnt understand the rule above at all :frowning: (it seems to only apply for a subset like extension, match, lambdas).

I would have loved it to be just the following, but, maybe I can find the reason behind in the long thread.

extension (x: Int):
  def foo..

given Foo[Int]:
  def bar..

trait A: 
  def foobar..

x match:
  case ..
  case ..

if a==b:
  ...
  ...
else:
  ...

but, there is probably some good reason why it wasnt selected. Ideally I’d love the following even more, but that could be more difficult to implement, especially if you are mixing braceless and braceful code in the same file :smiley:

extension (x: Int)
  def foo..

given Foo[Int]
  def bar..

trait A
  def foobar..

x match
  case ..
  case ..

if a==b
  ...
  ...
else
  ...

1 Like

No, the line ends with the identifier A, not trait.That’s why you need a :.

The second one doesn’t work though. If you add braces you still need with.

1 Like

thanks!

The principle of optional braces is that any keyword that can be followed by { can also be followed by an indented block

Does this apply to collective extensions?
They don’t seem to end with a keyword, yet don’t require a colon.

This is my biggest (only?) mental stumbling stone with indented blocks – I would actually appreciate any tips for how to adapt my mental model :slight_smile:

1 Like

I believe the difference is that the body of a class, trait or object can be empty or non-empty, while extensions are always non-empty.

It would be great to have such explanation in the official documentation.

2 Likes

In the interests of not rewriting history, I feel I have to point out that a considerable portion of the posts in that thread were variations of “please don’t do this” and “this should be behind a flag”, rather than experimentation or discussion on the feature itself.

4 Likes