Feedback sought: Optional Braces

I think I’m finally ready to put in my two cents. I think it would be easier to answer questions on Stack Overflow if we go with with than if we go with :. Easier as in simpler answers.

One of my most upvoted answers on it is about symbolic operators. Though the comments on the answer have been pruned, it strikes me that almost all criticism I have seen was about symbols with more than one meaning. It really bothers people when they have to figure out the meaning of a symbol from the context where it is used.

4 Likes

That’s part of the reason I reject the idea that significant indentation is a good idea.

For me it’s not just the one space variant that’s harder to see visually, I have a very hard time visually distinguishing blocks based on indentation. Without an easy way to reformat the code to put the braces back in, any code that leans heavily into the optional braces style is going to be literally headache inducing.

I’ve been following and I’m afraid I’ve lost what’s proposed. Can someone please post a link??

The thread was restarted on January 6th with this post. If that link doesn’t work, just use the slider to go back to that date.

So you actually figure out what the block is based on the braces? Like, just counting nested braces until you find the matching brace? I can’t even imagine that.

I like significant indentation because indentation is how I figure out what the block is. If there’s a lot of nesting, I might look at the vertical bars provided by the IDE, but never at the braces itself.

The closest I come to looking at braces is looking at IDE hints for matching braces, which is something that does not actually depends on braces. I might do it when there’s a lot of nested braces on the same line, in which case significant indentation is not an issue, or when there’s missing or extra braces, in which case the code would be correct if it was just significant indentation. Regardless, when the IDE is showing matching braces, all it is doing is showing the scope and it doesn’t actually need the braces to do that.

It’s actually much simpler than that, what causes problems is something like this:

class Foo:
  def foo = ...
    def ensureA: F[A] = 
      ...
        ...
        ...

    def ensureB: F[B] =
      ...
        ...
      ...

    def ensureC: F[C] = 
      ...
      ...
        ...

    for
      a <- ensureA
      b <- ensureB
      c <- ensureC
    yield baz(a,b,c)
    
 def bar = ... 

I have trouble with eyeballing stuff like if the for is part of ensureC or directly inside of foo, or if ensureB is still inside foo or something new, or if bar is still part of foo, etc.1

So I don’t need to count braces, just glance up to see if there’s a close brace. Most of the time that’s enough. If there’s a bunch of close braces, the close brace gives me something to mouse over so the IDE can either highlight the corresponding open brace or show a tooltip with the definition it ends. A couple empty lines don’t provide either the visual cue2, or a hook for the IDE to help me out.

Counting the vertical lines doesn’t really work for me because, after about 3 lines, they start to blur together. It’s a visual processing thing, not a vision thing (sort of like dyslexia) so it’s not something that corrective lenses would fix.

Clarifications:

  1. This is admittedly a bit of a contrived example, I didn’t have time to create something fully fleshed out. When I tried out the optional braces style, I found this to be most troublesome with nested functions, and most common in with files that contained nested definitions like ADTs, or when multiple implementation classes were contained in a companion object.

  2. A couple empty lines won’t provide that cue after this change, they do in Scala 2 because dropping the braces only compiles if it’s a single expression. In those cases, I do prefer no braces, because I don’t need the extra context, however this no longer applies in the optional braces style.

I agree. For small class declarations, : is more natural than with. I believe overall both : and with are very reasonable choices that together dominate everything else that was proposed. That’s why it’s so hard to choose between them! But in the end, consistency is the most important criterion.

4 Likes

Completely agree that in your example it’s hard to see where a scope ends. But:

  • Editors usually display vertical lines on the left, which helps a great deal, and
  • It is recommended to use an end marker to end any scope that has embedded blank lines, like in your example.

So I have not found it to be an issue in practice. Quite the contrary. With judicious use of end markers it becomes much easier to know what scope a definition belongs to than if one uses braces.

Indeed if we forbid one space as indent, perhaps we could do without with:

class C[A](x: A) extends T
  def f = ???
2 Likes

What about if the rules here http://dotty.epfl.ch/docs/reference/other-new-features/indentation.html#indentation-rules were slightly changed so that:

  • 1.second bullet was “the first token on the next line has an indentation width strictly greater than the current indentation width + 1.”
  • 2.first bullet is changed to “the first token on the next line has an indentation width strictly less than the current indentation width - 1, and”

By requiring at least 2 spaces, the ambiguity problem should be much less problematic. What do you think about that? Or have I missed some other new problem that this creates?

Is there anyone who actually is considering using only one space indents? So whatever we decides on with vs colon vs nothing, I think it makes the significant indentation more robust to introduce the “at least 2 spaces”-rule.

3 Likes

I don’t think this is necessary. The rules are robust enough as they are. Also, we don’t prescribe spaces, the whole scheme works as well with tabs.

Ah, yes; forgot got about the tab vs space issue :slight_smile:. But if the rule could check if an indent is at least 2 spaces or a at least one tab, then perhaps the ambiguity problem is diminished so that “no token” could come back in the game, so to speak. It is arguably the most regular/consistent solution…

5 Likes

Totally agree with this @bjornregnell

1 Like

They will be once Change doc pages for syntax and indentation by odersky · Pull Request #11197 · lampepfl/dotty · GitHub is merged.

As noted previously, I’ve found vertical lines added by the IDE to be spectacularly unhelpful, as they tend to blur together after about 3 of them. While I don’t know how widespread this difficulty is, it’s also an issue with things like git tree, and I know I’m not the only one that finds those hard to read.

The end markers are not as helpful as you might expect, because they rely on the author anticipating the needs of the reader. If a codebase is leaning into optional braces style, it’s probably because they find it easier to parse visually, so I doubt end markers will actually be used in all but the most egregious cases. While I’m admittedly likely to need this sort of marker more often than average, it’s going to be a problem basically any time the author needs this help less than later maintainers.

While that’s nice in theory, the visual ambiguity isn’t strictly limited to those cases. ensureC could have been written as a single statement, and it wouldn’t have mattered because the potential for embedded blank lines is present. Even if foo had an end marker, it wouldn’t help because unless it’s marked end foo, it could just as easily end ensureC.

1 Like

I agree that consistency is a good thing. However, I also think that being “beginner-friendly” is important, and I am concerned what the consistent-yet-clunky class A with syntax would do for Scala’s reputation and popularity for new users. Classes are a basic feature of most programming languages and the syntax for defining them should be simple and familiar.

So if : is out of the question, I think the “no-marker” variant should be considered again. I don’t quite understand the arguments against it. (It is of course possible that I have not thought deeply enough about it. I think there might have been a post in this thread explaining it, but I can’t find it.)

class A with
 def foo = 1
object B

In the above example, would not also a “single stray space” (i.e. before object B) change the meaning? What difference does the keyword with make?

3 Likes

I was originally more opinionated but I’m now of the opinion that whether we choose : or with wont really matter. They both work for slightly different reasons and after a while, we’ll all just get used to it and not even really see it anymore.

I think the bigger problem is with consistency from a users point of view. Whatever style we choose for class and object should also be applied to given and extension blocks. I know from the compiler author’s point of view there’s some objection in that the semantics are slightly different, I think @odersky doesn’t consider an extension block as being “a new scope” where as he does with class. From at user’s point of view (at least from my perspective), they’re all the same. To me a indented block is a new scope and if it’s technically not, I don’t really care (nor do I think users in general are going to care either). I think both Scala beginners and Scala veterans would find it easier and more regular to just remember to use one style (: or with) to mark a line as having an indented body. Trying to remember that extension blocks need to be declared differently than class blocks because of some implementation detail that isn’t important to me is a situation I hope we can avoid.

Of course there’s always the danger that when one tries to speak for many based on personal intuition there’s a chance you’re completely misrepresenting the many. :slight_smile: Please chime in if you disagree or maybe like if you agree. I’ve come to believe that this decision is going to be more impactful than : vs with.

10 Likes

Why did the coder use optional braces? …

This space intentionally left blank

A: To keep their pants up!

The thread is so long, I don’t remember if someone already told that old joke.

In fact, the thread is so long, somewhere near the beginning of it is where someone invented the original joke, although I first heard it as why the fireman wore red suspenders. That was a while ago, hence fireman and not firefighter. It’s funnier with purple suspenders.

I hope dcsobral got a couple of gumdrops back when he put in his two cents. I still match braces with % in vi.

However, it’s also true that I misconstrued some sample code from a ticket, where the indentation made a difference, and someone had to tell me to shift it left to reveal the bug.

I can already hear myself saying in desperation: Siri, where is the end of this block?

And she will reply: “At Sunnyvale and Fremont! Ha, ha. I can tell you many jokes, just ask me.”

I agree with japgolly, opinionated Scala has cured me of holding opinions.

One idea that hasn’t been floated is to code in github-flavored markdown. Then however it renders is the significant indentation you get, and therefore the inferred block delimiters.

The beauty of that solution is that your code samples are guaranteed to render correctly in the docs.

Since my Parisian friend appreciated this pun, I’ll repeat it here:

This space intentionally Left Bank

That must be an old one, but google doesn’t find the t-shirt for me.

2 Likes

I think one problem is with backwards compatibility. Say your old code has a stray whitespace, like so:

class A
 object B

It is going to be interpreted differently by Dotty than former Scala versions.

2 Likes

Perhaps this could be helped by a migration warning? Stray spaces in accidental indentation is after all detectable by a compiler and Scala 2.13.5 could issue warnings.

Also, it would be interesting to see in the community build, how many actual cases of stray spaces there are.

And if we change the rule to: “at least two spaces or at least one tab” then the problem might be even less in practice.

7 Likes