Scala 3 quiet syntax

I was reading the documentation at control-syntax-new.html.

Assuming that this is the latest, I note:

The condition of an if-expression can be written without enclosing parentheses if it is followed by a then or some indented code on a following line.

But, The condition of a while-loop can be written without enclosing parentheses if it is followed by a do.

This is not a big issue, but I was wondering why do cannot also be omitted (like then) if it is followed by some indented code. Would it make the language more regular if then and do were treated in the same way?

Thanks,
Rob

1 Like

In fact, I think we’ll got back on always requiring a then after the if instead. Writing then is initially easily forgotten, but in the end the better uniformity wins. So, the following is valid:

Old style:

if (condition) 
  thenPart
else
  elsePart

New style

if condition then
  thenPart
else
  elsePart

But the following would be phased out:

if condition
  thenPart
else
  elsePart
5 Likes

That sounds good, thanks.

If there is time to tweak the links on the Dotty documentation pages to point to the current proposal that would be great, but I appreciate that you are very busy :wink:

Here’s a vote for high style over uniformity.

1 Like

What about colons?

if condition:
  thenPart
else:
  elsePart

Also, would it be possible to combine the no-enclosing-parenthesis syntax with curly braces?

if condition then {
  thenPart
} else {
  elsePart
}
1 Like

Colons are for type ascription. As such it’s not a great idea to use them to introduce blocks.

8 Likes

If we are so desperate to get rid of curly braces that we even consider replacing them by keywords, how do you feel about introducing keywords begin and end?

I’m not suggesting doing that. I’m just curious how people would feel about that.

I don’t understand why we would give up the simple, clear and consistent and very common syntax based on curly braces and replace it with a wild mix of curly braces, indentation and special keywords.

11 Likes

See -Yindent-colons. https://dotty.epfl.ch/docs/reference/other-new-features/indentation.html

Not only does doti fork the language, they re-introduce forking -Y!

2 Likes

I thought that wouldn’t be a problem, as this is usually not a part of an expression but a part of a definition, but then there are anonymous functions:

if myList.exists(a: MyType => ...):
  thenPart

Yeah, this looks a bit confusing.

1 Like

I think this specific syntax is more about removing the enclosing parenthesis rather then omitting curly braces; this is why I asked if it would be possible to mix curly braces with the then syntax.

I would much rather see

xs.map { x =>
  val y = x - 1
  y * y
}

than

xs.map:
  x =>
      val y = x - 1
      y * y

Besides, this entire feature (optional braces) is still quite controversial.

5 Likes

Slightly besides the point, but I’ve proposed @ for this use case before. I think it looks nice, and it is backward-compatible (won’t break any code):

xs.map @ x =>
  val y = x - 1
  y * y

The rule would be: Anything that comes after @ (including indented blocks) is considered wrapped into a set of curly braces. Also, @ has least precedence and associates to the left.

So

xs.foldLeft @ init @ (a, x) =>
  val res = a + x
  res

means

xs.foldLeft { init } { (a, x) =>
  val res = a + x
  res
}

As for if/then/else, I think it’s perfect as it is.

3 Likes

Looks like the @ sometimes closes a previous place and sometimes it does not. What if I want to open a second curly brace without closing one?

2 Likes

That’s because it is left-associative. As with any operators, you can’t have it sometimes associate right and sometimes associate left.

Perhaps it would be more useful as a right-associative operator (like Haskell’s $), but I am not sure.

It’s almost like you’d want two paired operators. We could bikeshed that a bit, and I’m willing to kick it off by suggesting {} - there’s plenty of prior art, and it wouldn’t break any code :wink:

2 Likes

No, I do not. The entire point is to avoid them.

The problem of paired operators is that they accumulate in annoying ways, making it harder to see which closing brace closes which opening one.
For example, compare f(a)(g(b)(h(c)(d))) with f(a) @ g(b) @ h(c)(d) (assuming right-associativity).
This is not to mention the fact that we’d like to avoid having to terminate functions passed as arguments with an extraneous } line, leveraging indentation syntax instead,

There’s also plenty of prior art with $ in Haskell.

2 Likes

Maybe we can have an IDE plugin that makes curly braces look like something else.

2 Likes

The issue I have with these sorts of examples is they don’t reflect the sort of code I tend to run into. Convert that example into something that’s a bit more realistic, and the braces cease to be an issue (particularly with something like the Rainbow Brackets plugin):

input.fold(inputIsEmptyError) { 
  _.entries.fold(entriesAreEmptyError) {
    validateEntries(allowFoo)(_)
  }
}
3 Likes

I’m finally getting around to playing with Dotty as I look at book revisions for the third edition that will use Dotty and I ran into an error that really worries me. I’m putting it here because it relates to the quiet syntax and meaningful whitespace. Previously I have voiced the opinion that I’m not a fan of meaningful whitespace, but I was always under the impression that if one was using the “noisy” syntax nothing would change and this wouldn’t cause a disruption to those who don’t want to move over. Then I got the following error message:

[error] 7 |		val v = new Vect2D(3,4) {}
[error]   |		^
[error]   |		Incompatible combinations of tabs and spaces in indentation prefixes.
[error]   |		Previous indent : 4 spaces
[error]   |		Latest indent   : 2 tabs

I get this by adding a line, using vim, to existing code. The existing code uses spaces, vim, by default, puts in tabs. As an experienced programmer who happens to use the tab key for indentation, I find this behavior a bit annoying because code that is copied and pasted will almost always have spaces for indentation. I can change some setting and get around this though. As an educator who tries to teach novice programmers, I see this an an absolute nightmare. Any time default configurations for tools produce the wrong behavior it means a bunch of headaches for both students and teachers. Yes, the error message is very clear, but this means we either have to have every student change the behavior of vim or stop using vim. I will note that if I edit the file in VS Code everything is fine as it convert my tabs to spaces by default.

What isn’t obvious from this error message is that this code is in curly braces. There is no reason for the compiler to enforce indentation rules because other syntax elements are making it clear where the block begins and ends. The decision to enforce the rules of meaningful whitespace in situations where they aren’t needed as the default behavior seems to me to be very problematic from the education standpoint even if it is only mildly annoying for experienced developers. Is there any way to make it so this rule is not enforced by default for blocks inside of curly braces or has this ship already sailed?

I will note that this project is using 0.25.0-RC2 in case that matters.

5 Likes

There is -noindent, but I was going to say maybe this is just a bug, which is mistaken.

The forum tells me I already pasted the link which says the indentation prefixes must be comparable. “To avoid such errors, it is a good idea not to mix spaces and tabs in the same source file.”

This is why it doesn’t just ignore what is between braces:

In a brace-delimited region, no statement is allowed to start to the left of the first statement after the opening brace that starts a new line.

This rule is helpful for finding missing closing braces.

Maybe that means it should only enforce the rule when there are missing braces to find.

It looks like the flags are intended to be ergonomical. I don’t understand all the dimensions of use cases, but probably you’re not supposed to be unpleasantly surprised. So maybe it is an ergonomics bug.

val rewriteNoIndent = ctx.settings.noindent.value && rewrite

val noindentSyntax =
  ctx.settings.noindent.value
  || ctx.settings.oldSyntax.value
  || (migrateTo3 && !ctx.settings.indent.value)
val indentSyntax =
  ((if (Config.defaultIndent) !noindentSyntax else ctx.settings.indent.value)
   || rewriteNoIndent)
2 Likes

I think that this is just a matter of ensuring vim is setup sensibly.

I actually like the fact that Scala is enforcing that the indentation to be consistent, and given that the mapping from tabs to space indents vary, forcing one or the other to be used consistently generally seems helpful.