Make "fewerBraces" available outside snapshot releases

This topic seems to be the best one about feedback for fewerBraces, so I’m posting here.

For the sake of evaluating the implicit proposal behind that flag, I have experimented with it in one of my personal projects. As a stylistic exercise, I have tried to remove every single brace in my 8 kLoC codebase. I did manage to do so, although I am clearly not sure whether the price was worth it. Also, as part of my experiment, I made a point of not moving things around in vals, or replacing braces by parens (unless I absolutely had to, see below).

I made a series of changes by category, gathered in a series of commits. The codebase as a whole is private, so I won’t share it. But you will find the diffs of each commit in the following gist:

Without further ado, here is my feedback, as a commentary on each of those commits. I am not discussing the readability of the resulting code in that list. I leave that as a more subjective discussion towards the end of this post.

1. Use fewerBraces in places where it works as intented out-of-the-box

In the first commit, I applied fewerBraces to all the places in my codebase where it was clearly the intended use case of the feature, and where it did not require me to do any other change (including formatting changes). In a sense, this is where things went well.

2. Reformat call chains to admit more uses of fewerBraces down the line

This commit is somewhat unrelated to fewerBraces. It is purely a reformatting of places where my call chain coding style prevented to use fewerBraces. So I reformatted those call chains to make them amenable to the fewerBraces changes down the line.

It’s worth observing that these changes introduced more lines, and more indentation levels. This is a problem I already had when adapting my coding style from the general brace-based syntax of Scala 2 to the indentation-based syntax of Scala 3.

3. Use fewerBraces where it works after the previous reformatting

This is like the commit 1., with new spots enabled by the reformatting of commit 2.

4. Where I had to avoid using an _ as lambda param name

This commit exhibits two instances of a bug involving _. I filed it here:

5. Where I had to use a colon because of a partial function argument

This commit shows a bunch of places where I pass an anonymous partial function as argument. In those cases, I had to use a :, although I think we could get away without that. I filed that as a feature request here:

6. Where the placement of commas goes very wrong

This commit shows several places where leveraging fewerBraces leads to argument-separating commas to be displaced to very misleading places. The typical shape is like this before:

foo(
  xs.map { x =>
    x * x
  },
  otherArgument
)

which becomes

foo(
  xs.map x =>
    x * x, // <-- misleading comma here
  otherArgument
)

This can also happen with trailing commas.

This is not something I have observed with any other language feature before, including the general indentation syntax of Scala 3. I must say that I find it quite concerning.

7. Where I was forced to use new parens because of curried params

This commit shows the only place where I could not avoid introducing new parens to compensate for the braces. This happens because the lambda argument is followed by another parameter list.

This can be quite common because of methods like groupMapReduce, which takes 3 different functions as arguments.

(There exists another way, which involves putting a : alone on a line, but I found that way too horrible to even consider.)

8. Where it goes very wrong because the lambda is an argument of an infix method

Finally, the last commit shows some places where a lambda is an argument to an infix method—a common idiom in Laminar, for example. I have filed another feature request to make this better:

My subjective opinion on the resulting readability

Disclaimer: I was very much against the introduction of the indentation-based syntax in Scala 3. I still think it was a mistake. Not because of the resulting syntax (I use it in that codebase, and it’s fine) but because the change of syntax would inevitably introduce more variability in Scala code styles.

For fewerBraces, I’m not so much against the change. TBH, I think we went so much over the line with the general indentation syntax that a few more changes with fewerBraces won’t make a difference.

Subjectively though, I find the resulting code simply harder to read. While it may be an artifact of my experimental methodology, I strongly feel that fewerBraces is there to get rid of braces for the sake of getting rid of braces—and not to actually improve the developer experience or the readability of the code.

Compare for example

    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

with

    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

The braces in the first snippet give some visual structure, and help associate the various method calls that belong together. In the second snippet, I have to make quite a bit of extra effort to associate the method calls where they belong.

A counter-argument would be that I should put the result of the other flatMap in a separate val first, then call .toSet on that. I don’t accept that argument, though, as it makes the structure worse, and essentially forces me to write even more code, with less structure, to recover the readability loss of fewerBraces.


The misleading commas are also very problematic to me in terms of readability. It becomes quite a lot harder to make a mental structure of the code at a glance when those commas are misplaced.


I also don’t like that this syntax effectively makes multi-function methods like groupMapReduce second-class citizens of the language. There is no good alternative for those. It’s really the language changes telling you that you should never even have been using such methods! And that despite the fact that they are in the standard library.

Summary

As a summary, I would say that I am not at all convinced by this feature. I will repeat my main impression of the feature here: to me, it feels that fewerBraces is there to get rid of braces for the sake of getting rid of braces—and not to actually improve the developer experience or the readability of the code.

13 Likes