End markers for fewer braces blocks

I am a heavy user of optional braces, and now fewer braces as well.

But I am concerned about readability of code that uses fewer braces with large blocks of code, as there is no end marker that can be used with them.

I’m raising this now as this style of code should become more common as more code adopts boundary/break.

But also testing frameworks like MUnit, which rely on by-name parameters, are affected:

class FooSuite extends munit.FunSuite:
  test("this test is very long"):
    locally:
      ...
    locally:
      ...
    ...

  test("second long test"):
    ...

I believe that the syntax should provide an optional way to close these anonymous blocks if you are using the fewer braces style.

the least “ambiguous” syntactically would probably be end do, but do may be surprising as there was no “starting” do, but I’d say end do would then unambiguously mean “anonymous function”. (or perhaps we introduce at the same time an optional do before anonymous functions).

Perhaps the method name could be used (e.g. end locally, end test), but I think this is problematic because up to now this exclusively meant “end the definition foo”, never application of.

@odersky previously suggested allowing end on its own, and if the parser can do it then maybe that is best. However this is not backwards compatible because end on its own is treated as a normal identifier.

Looking forward to the community’s suggestions!!

5 Likes

Perhaps end block?

3 Likes

Or perhaps end apply

Why would that be a problem? I think that would be the most informative option.

3 Likes

the method name would be good for DSLs I agree.

One issue is what if there is no explicit method name, e.g.

val res = ((fn: (Int => Int)) => fn(23)): x =>
  x * 2

assert(res == 46)

in this case, I believe no end marker would be valid. So I propose in that case to simplify checking only match when it is of the form foo.bar(...)(...)

Not trying to be snarky, but I’m genuinely curious: why not just use braces at this point?

test("this test is very long") {
  locally {
    ???
  }
}

is the same number of lines as

test("this test is very long"):
  locally:
    ???
  end
end

Is the latter more aesthetic in some way? Is it the pointy bit of the curly braces that offends?

8 Likes

Yes, the point is the pointy bit at this point.

Also, one would like to freely convert between braced/braceless without loss of clarity.

I would omit the second, extraneous end in your example. The beauty of optional things is in opting out.

I think this need has arisen before, but I can’t find a ticket. There is a ticket for begin syntax, where the question was how to mark the end of a constructor, end <init> (as opposed to the end of the class definition, end C).

For the interaction of end with operator syntax:

I hope to come up with a mnemonic like My Dear Aunt Sally but for end markers.

1 Like

An end token is optional, but a curly end brace is not. This is significant with the combination of scala-fmt that can add end tokens automatically. So as I write the code, I can use a colon and indent and not worry about closing the brace and having all closing braces align correctly, and scala-fmt can still observe this as valid code and automatically add the end token.

4 Likes

There are some older languages that allow “end” alone to end a block or “end <keyword>” or “end <name>” to end a block with the appropriate matching item (or error if it’s a mismatch).

This approach could work here to allow specifics for clarity when possible but still work for anonymous things, without allowing a free-for-all in what follows “end”.

1 Like

As an outspoken curly braces hater I would switch back to them instantly in case I would encounter code like the above.

End markers are many times worse than curly braces!

At least you can write the curly nonsense like }}}. But you can’t do that with end markers. Also they are highlighted as keywords! That’s infinitely annoying! (The braces nonsense is at least grayed out with most code highlight themes.)

I would not care if there would be some tooling solution against all that curly and end nonsense. But this tooling solution needs to be in place before anything is done which would make thing even worse than the current situation!

People are so quick to ask whether a computation terminates, they forget to wonder whether it terminates with annoyance, or possibly annoys without termination.

Elsewhere, it was suggested that if you’re writing blocks of code, you’re doing it wrong. It doesn’t matter how you’re demarcating the blocks.

I’ve become a fan of “chaining” syntax:

def f = new Thing().tap(initTheThing)

Rather than scan to the end of a block, the lead expression tells me what the result is.

Analogously to “meth heads” for those who love to decompose their methods into atomic bits, we must identify the other crowd, who prefer to sequence their statements, as “block heads”.

1 Like

It sounds to me like engineering sophisticated rules to provide an alternative option in situations that braces can easily disambiguate today is adding questionable complexity to the language.

As an outsider, a big part of my learning process is to recognize the idiomatic patterns of the language. Adding different flavors of syntax to accomplish the same semantics is impeding that goal because different people like their meals with different spices.

As the title mentions “fewer braces blocks” and not “braceless blocks”, I think one can justifiably wonder whether braces might simply be the right tool for the job. Parentheses are used to disambiguate expressions everyday and most people seem to be fine with that.

8 Likes

In languages-with-braces, it is very common to add a comment after the closing curly brace under some circumstances. When would you do that? Simply, if the opening brace is out of sight and the indentation level does not give you any clue. Adding the comment is a nice gesture towards the reader in such a situation. Obviously, such extended code blocks should be the exception; yet for every rule there is an exception… and typically code grows old in tiny incremental steps.

If I understand correct, this proposal is precisely the equivalent to this practice. With the added benefit that its correctness is checked. Following this reasoning, just using a curly brace would not help in such a situation; you’d have to add a comment to indicate what is closed here. Even without the curly brace, you’ll always see from the indentation that something ends here, just it can be hard to figure out what exactly is closed when the nesting is several levels deep.

5 Likes