Feedback sought: Optional Braces

Is it? I wasn’t aware of that. I don’t want to open the pandora’s box given I seem to be missing information here but why isn’t indentation strictly set to n-spaces (eg 3) for any use case? A 1-space indent, if this is what you are saying is possible for class definitions, should just be banned. Then either you 3-indent and this is your class body, or you don’t indent and this is out of the class, or you incorrectly indent and this is a syntaxic error.

2 Likes

It’s more dynamic than that. The indent for a block is defined by your first line. (I think it is on a per-block basis, someone correct me if I’m wrong). Otherwise we’d have to decide in advance what the correct indent is, knowing that 2 or 3 (or 4) is already controversial.

Rather than start another pointless bikeshed-colouring holy war, I wanted to ask again why this could not be a compiler flag? That way, the default can be 3 (seems to be the current favourite), but people who have opinions could easily switch, rather than spending all that energy on diatribes why X spaces is “obviously” a ridiculous choice.

Edit: I’m expecting the complexity of this switch to be low (I may be wrong…), otherwise it would obviously be very very late. :upside_down_face:

1 Like

Or one may exclude single-space indentation from qualifying as an indent token. Who ever would do such a thing? I find it hard to believe that we are having all these discussions about “with” or “:” just because “a single space for indent is too dangerous” when one could either make indent-size a compiler flag by default set to 3, or simply prevent single-space indents and call it even.

Am I missing something here? Considering my above example with a conditional statement, I don’t understand the effort being put in to protect for erroneous indentation when there is a symmetric risk of erroneous lack-of-indentation which we are perfectly willing to accept and is the even concept of significant indentation.

Just make significant indentation really significant, and optional braces really optional! No keywords, no nothing :slight_smile:

4 Likes

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 https://github.com/lampepfl/dotty/pull/11197 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