Make "fewerBraces" available outside snapshot releases

I actually think the second version is perfectly fine. One can add braces if one wants to but I don’t think I would miss them. I agree the version with end markers would be a non-starter, though.

What I like about the second version is the regularity in the sense that the “.” always align in the same way. Single line chaining:

abc
  .reverse
  .toSet

Multi-line chaining:

abc
  .map x => 
     ...
  .toSet
5 Likes

TBH I think part of the reason this looks bad is the small 2-space indentation. 4-space indents make the grouping and scopes very clear (at the cost of pushing things to the right):

val perCompanyOrNeutral: Set[AssetType] = Company.AllCompaniesAndNeutral
    .flatMap company =>
        companyDamAssetNames(company)
            .map (level, assetName) =>
                AssetType.Image(assetName, AssetPath(s"assets/dam$level-${company.stringID}.png"))
            .toSet
    .toSet

I don’t think this 2 vs 4 space thing is unique to Scala. I’ve worked with Python and Coffeescript and FSharp codebases in the past, and uniformly those that were 2-space indented were hard to parse visually, while the same code after reformatting to four spaces OR adding braces/parens things looked much clearer. And saying it’s “up to the user” is a non-answer: users follow leadership, and if prominent Scala 3 projects such as the compiler the compiler are using two-space indents, everyone else will as well.

For whitespace syntax as a whole, I’m against using it for the same reason as Sebastien (among other reasons). But I have the opposite conclusion: I think we need to go all-in with fewerBraces if we want to make it usable at all. Consider this random piece of Scala 3 code “in the wild”:

In one 20-line snippet, we can find all three demarcation styles: parens, curlies, and end markers, all variously nested and mixed together. This is literally the worst-of-all-worlds, taking the syntax of Java/Python/Ruby and combining them into one big jumble. Sure it could be prettied up some, but the reality is that code in the wild will be a bit messy, and there’s no reason to believe the code I work with day-to-day will look much better than this. The various Scala 3 contributions to my com-lihaoyi projects have a similar hodge-podge of styles.

The “people can use indentation, or braces, or end-markers, whatever they like” approach, while nominally provided flexibility, is a curse that destroys all consistency in a codebase. If we want the benefits of Python-like syntax and simplicity, this isn’t it. While simplifying migration is nice, it cannot come at the expense of migrating to something worse than the status quo.

The way we get out of this mess, assuming we don’t want to abandon indentation syntax entirely, is to go all in. If we want Python-like syntax, we should go for Python-like syntax: only indentation, 4 space indents, no mandatory braces, no other end markers. Braces would be as common as semicolons are today: used once in a blue moon, but actively discouraged and never necessary.

There will be some issues. Some can be solvable:

  • The single-line colon : for calling multi-block functions like groupMapReduce can be given a nicer syntax (I have proposed do in the past, conveniently free since the dropping of do-while loops, but we can come up with some other keyword/marker).
  • The increased indentation could be partially compensated by other techniques, e.g. top-level methods, indent-less match/case statements, there are probably other clever things we could do here

Some may be unsolvable:

  • Passing multi-line lambdas to functions followed by a comma will always look a bit weird.
  • The method-chaining with indent-delimited blocks looks a bit unusual, but with larger indents I think we can get used to (having used that exact syntax in Coffeescript before myself)

But that’s the only way I see that we can do a forward-fix to make Scala’s indentation syntax nice. Otherwise I can’t see myself using or recommending people to use whitespace or end-delimited blocks - the result is just too messy.

If the codebase uses a formatter, I don’t see how that is the case. Across codebases, sure.

Time to rename Scala 3 to Jambalaya.

Since I tend to add end markers, I wondered if that is my end marker, and git blame says yes.

So I can attest that this is not a high quality for a style test, as it shows me learning Scala 3 during the pandemic. The closing brace is “legacy”, as it is not usual to format the entire file when making an edit.

On the Dotty project, I have encountered the pushback that “this edit is not minimal”. So even my sense of a good compromise does not meet everyone’s standard.

I have recently come around to arranging my dots vertically instead of horizontally in “fluent” style. I previously thought of vertical as Java style because it’s the verbosity that requires it. I think I switched because of using less infix (although leading infix improves vertical infix), and now I would be willing to follow anyone’s lead in using indents of 3 or 4. Probably 3 just to remain different from Java. Oh, and 3 because it’s Scala 3.

2 Likes

“Mess” is also a food word for melange or mixture. Or actually, my high school Webster’s says it is just a course (of a meal), with the same root as message. It doesn’t explain why it means opposites: laying out a meal is an arrangement, and getting everyone to table is perhaps as large a task of assembly. The mess is the remains afterward.

One meaning is “enough” food: “a mess of peas” is a measure of what suffices for supper. In one of Joyce’s Dubliners stories, a fellow pops in and orders a plate of peas. That sounded so strange to me, to order peas. It doesn’t say if he ate them with a fork or spoon, or a knife coated in honey. I guess it would say if it were a honey-coated knife.

The dictionary entry directs one to “smite” and then “smegma”. TIL “smegma”. I would be comfortable saying Smegma 3 for Scala 3.

The food word I was thinking of is “hash”. That word means “ax”. I think of a “hash” always as a “re-hash”. “A restatement of something that is already known.” A hash code is such a restatement. As food, I assume a hash is something leftover, chopped up and added to the pan with some primary foodstuff. Fried potato at breakfast is a hash brown.

As a bonus word, the closure in the car port is called a hasp. I don’t know if I knew exactly what a hasp was. But if I were to come up with a hybrid of Haskell and Lisp, it would definitely be Hasp.

Edit: So my point is that I see it as a mess only in the sense of “a mess of peas.”

1 Like

That example needs some context to be understood properly. In dotty we made the decision NOT to reformat globally to indentation in order to keep meaningful git history. But every new or changed piece of code uses indentation style. For people used to the codebase that’s actually an advantage since it gives you a sort of archeological layering of code :wink: . Anyway, I would not treat that as typical.

Generally feedback on indentation was very positive so far, so there’s no way we would go back to braces only.

The issue here is that what you described sounds very typical. Basically all my OSS codr, and all the proprietary code I work on, we have a lot of old code and want to preserve VCS history. I imagine that is the same for all the Scala projects in existence today. What then?

As we’ve seen from @som-snytt, best practices around “minimal diffs” contribute to the mixing, as even working in a file for months you won’t have a consistent syntax. And lampfl/dotty is not special: we have similar “minimal diffs” conventions in all my com-lihaoyi projects, as well as my company’s codebases. Far from being special, what you describe about Dotty sounds like the most typical codebase imaginable.

I don’t mind if we need to put some work to have a smooth migration, but what I’m seeing here is “(a) ruin your git blame, (b) give up your PR best practices, or (c) live with a hodge-podge of syntaxes” as the current migration proposals. I’m sure you can see that is rather offputting and a disincentive to migrate.

What I proposed above is to do aggressive migrations to a single new syntax. That avoids (b) and (c), and we can use Git’s --ignore-rev to mitigate (a). Together with fewerBraces, that will give us files with a nice python-like syntax. And as community leaders, it is on us to come up with recommendations, so people can smoothly migrate without re-inventing half-baked migration strategies themselves.

Anyway, I’m here because we have a few million lines of Scala we may have to migrate at some point, and I want to talk about options and possibilities. I don’t want to re-litigate old decisions. I just hope others are open to engage with me on these topics as well

4 Likes

Yeah this didn’t exist when we made the decision to avoid a bulk rewrite of dotty, but nowadays it is even supported by github (Ignore commits in the blame view (Beta) - The GitHub Blog), so that decision could be revisited.

2 Likes

I was against that for indentation in general, and I am still against that. This essentially proposes trading vertical space (braces) for horizontal space (more indentation). The latter resource is the scarcest, so this is entirely the wrong trade-off!

You can get that regularity with braces as well, if that’s really the important thing:

abc
  .map { x =>
    ...
  }
  .toSet

It does use one more line, though.

While I agree with your point about indentation versus braces, IMO presenting end markers as a third option is misleading. end markers are a part of the indentation-based syntax. They provide a visible ending to larger pieces of indentation-based blocks. They even provide some added value compared to braces, in having a compiler-checked repetition of the name of the thing that is being closed. They are extremely helpful in an indentation-based codebase that exceeds one screen of vertical space.

5 Likes

I hate end markers so much. I wish they were not introduced. The convention could have been “if the block is too long, use braces”. There are automatisms in every IDEs for these. For instance in vi you can jump to the closing brace with %.

Further, I do not see the problem with mixing styles (indentation + braces). In Java/Scala2 you can already omit braces for single line blocks and nobody complains.

7 Likes

At least for me, the Java/Scala2 brace omission works because they are limited to single expressions. It’s much easier to parse with when you don’t have to deal with potentially jumping a visually indistinct number of scope levels whenever there’s a line break.

Very different situation.

1 Like

I agree with sjrd and rjolly except when I don’t.

Now (2022) I think if horizontal space feels scarce, it’s because the lines are too long. Adriaan duped me into 140+ width lines, on the theory that monitors are wider. But when I read a novel, I need a certain line length that is sustainable.

Previously, I did not like // end comments, but now I love end markers. The convention is, if the block is too long, use shorter blocks. But if you have a few local methods, with a // begin comment (because there is no begin marker yet), the end marker is indispensible. In 2022, a tool that can match braces can also match end markers with the corresponding definitions. I, too, have an overpowering nostalgia for “That '70s Tool”. But I don’t want to be constrained by it.

I would like to boost sjrd’s signal that end markers (and also optional braces) are optional. I appreciate the docs, which stage the feature as, first let’s check whether your indentation looks OK. Then, since we’re already doing that, you may omit braces. Then, for purposes of visual acuity, you may add back an end marker (albeit tastefully). Therefore, indeed, these are not separate features but of a piece.

The other style issue which goes back to time immemorial is what’s the optimal length for method definitions. Odersky says to name things but complained when my named expressions were too fine-grained. I started programming with C routines under about 200 LOC in a file. Now I prefer to contemplate only LOC that fit vertically on my screen. We should refer to “scroll-less coding”. Other dubious conventions include ordering definitions by access modifiers. Another rule that annoys me is that overloads must be contiguous in source. I would agree that overloads must be visible on one screen.

It would be wild if Scala’s name binding, which is sensitive to compilation unit, also respected what is on the screen. That is, I don’t want to be surprised by definitions in other files. What if I also don’t want to be surprised by definitions not on my screen?

Minimally, if the REPL now lets me view units of “lines that parse”, then my programming tool should also show me just the region of interest.

“Old” doesn’t mean “outdated”, we still drop a . at the end of sentences, and it’s “That 7th Century Tool” :wink:

Punctuation has stuck around because it’s useful, and as much as some limited parts of a codebase may benefit from looking like concrete poetry, I’d caution against expanding this particular experiment until it’s had time to bake and we find out if it’s actually as readable generally as it’s advocates promise, if the “cure-all” tooling that keeps getting mentioned actually materializes, etc.

I just want to put in my vote that significant indention is pretty ugly and unreadable, but I am happy that it has been included since I think it will help Scala stay relevant in this increasingly Python-dominated world.

I don’t mind many flavors of Scala, and I quite liked that Scala permitted e.g. infix and postfix ops, but that teams could decide not to permit them (or permit them in limited cases) using scalafmt, which helpful rewrites uses away if you ask it to. However, Scala 3 has taken the opinion that the language itself should participate in style enforcement, requiring infix and import postfixOps to continue to make use of them. This seems reasonable to me, because it is in the direction of code syntax that is less magical, ambiguous, and confusing.

Reading this thread, I’m worried that Scala 3 is trending in the other direction for significant whitespace: having the language encourage or even enforce the magical, ambiguous, and confusing syntax you get from significant whitespace – snippets like xs.map x => x + 1 trigger the same worries to me as ys concat xs map f, which is legal Scala 2, but I believe now generally looked down upon. I can’t tell you how many times I’ve gotten lost trying to parse multi-line nested for comprehensions in Python, though of course you can’t put all the blame on significant whitespace there. I similarly find the whitespace-heavy Haskell to be a huge pain to read.

All that is to say I sincerely hope that the plan is not to actively encourage or enforce the parenless syntax, so that those of use who don’t like it can set a flag in scalafmt that wipes it away and continue to love the language as much as we do. And if the language does want to participate in enforcement and not outsource to scalafmt, then I hope that it is in the direction of preferring the braces (i.e. requiring import language.fewerBraces, even if it graduates out of experimental).

3 Likes

I only have a hundred thousand and change, but same deal. Would rather migrate to a uniform community standard than contribute to a hodgepodge, even if nobody but me is looking at my style of Scala.

The awkward part is that although our Scala 2 code looks fairly similar (only fairly), since Scala 3 has more options, it’s likely to diverge increasingly much.

My personal rule has been: in conditions where the braceless syntax works well, use it. Otherwise, if it’s even a little questionable, don’t. This is basically the same view I use with dot-free style: use the simplicity only when it is a significant improvement to clarity.

So, for instance, I’m totally happy rewriting

def something(a: A, b: B): C = {
  val d = f(a, b)
  makeC(d, d, d)
}

as

def something(a: A, b: B): C =
  val d = f(a, b)
  makeC(d, d, d)

It’s just plain better. No downsides. Just like I’ll write xs.map(_ drop 5) instead of xs.map(_.drop(5))–the second adds a lot of visual clutter without clarifying anything whatsoever.

(In practice, “only when better” has tended to mean that I never use : to open a block. The just plain better cases almost all have an opening symbol.)

If I were to instead to try the all-in approach you suggest, the existence of unsolvable problems is, well, a problem. Code is already hard enough to understand in those kinds of problematic cases; that’s one of the last places where any loss in clarity is acceptable. The reach of what was clearly a syntactic win could be extended, though.

Note that anything that requires 4-space indents I would probably consider a non-starter, because I have plenty of code indented too many levels for 4-space indents to be viable. The level of rewriting required to lower the indentation needed exceeds what I’m likely to want to do for anything complicated enough to have needed such indentation in the first place.

5 Likes

Trailing comma is an unfortunate victim of the fewer and fewer braces movement.

I wonder if we can reinstate trailing comma before end markers?

Scala code often has a lot of indentation levels because of nested methods and objects, ifs, pattern matching, and especially higher order functions. With 4 space indentation you would need a second screen just to see the most interesting part of the code, which is usually the most indented.

5 Likes

My own two cents on this. I’m not a huge fan of indented blocks (I’ve written more Java and XML than Python and yaml), but I’ve grown to like it in Scala. I find that 2 spaces are not enough and 4 are too much waste. I’m just finishing a book in which I’ve indented all the Scala code with 3 spaces.

I don’t know how much more braces I want to see removed. Like someone said earlier, at some point it’s fanaticism, and makes code harder to read.

Still, I’m a bit annoyed when I change:

for (_ <- 1 to n) do
   blah
   blah

into

n times {
   blah
   blah
}

or when

def f = 
   blah
   blah

becomes

def f = synchronized {
   blah
   blah
}

Even if it doesn’t save a line, I’d prefer something like

def f = synchronized:
   blah
   blah
end synchronized

to the version with braces. It makes it clear that the body of the function is synchronized, arguably more than with the braces.

1 Like

As much of the discussion has been centered around professionals and legacy code, I’d just like to add my experiences from teaching one semester with Scala 3 and optional braces.

It is very salient that beginners with no previous coding experience (around 30% of my students) find the indentation syntax much easier. The ones with prior coding experience in C-like syntax have a period to adopt but almost all embrace the new syntax after a while. The ones that come from Python have a smooth transition.

I think that the colon is very intuitive and works very good as indentation marker, and in general students find it intuitive.

They tend not to use end markes spontaneously, but I asked them about code on my slides and most seem to think that nested structures get more readable with an outer end marker.

6 Likes

@odersky @sjrd I’d very much like to see fewerBraces available outside nightly, so I can use that in my teaching using stable releases. Is it possible to be included in the 3.2 series?

3 Likes