Make "fewerBraces" available outside snapshot releases

I think think this showcases an important misunderstanding: Your proposed parenthesis syntax is the brace syntax !
Let’s look at the original, un-de-braced code:

And now your final example with parentheses and removed braces:

The only difference is that the multi-line parens become braces!
This is something we learn really early in scala courses, and has not caused me any problems, I find it extremely clean!

Now you might retort that we shouldn’t use multi line lambdas, but as explained in the message your message replies to, this actually makes the code harder to read (regardless of brace/parens/indent)

2 Likes

I am not clear what you are disagreeing with me about?

My point was that parentheses are a solution. My other point is that the original sin was to repurpose braces to behave like a combination of parentheses and blocks in order to reduce symbols. But that decisions unfortunately has contributed to the sin of braces not being a bijection with blocks, which means we can not automatically translate braceless to brace style which thus causes a bifurcation of the Scala ecosystem.

Now with the braceless style, we do not need to use braces in that example and can use parentheses while also avoiding the doubled operators that would have been necessary in braced if the original sin had not been committed.

See there’s a chronology that is pertinent to understanding.

Your infix for chaining is elegant. I presume you realize how incomprehensible that is for someone coming to Scala for the first time if they had never been exposed to Haskell?

I would not consider that to be idiomatic code if we want Scala to be highly readable.

Perhaps we should only resort to that beautiful (but deceptive) infix chaining crutch when the alternative nesting too grotesque to bear?

(apologies for the reposts, as I am under a liver medication causing grogginess and slightly blurred vision)

Hmm, I couldn’t really give an explanation, but the infix one does look more legible!

The chaining with <indent>.map x => “reads” badly tho

But your infix-chaining example in the other post looks really good!

The only fear I have is that it looks really legible with known functions in snippets, but potentially really hard to parse in real codebases (both for humans and machines)

So on the whole I’m very hesitant

My disagreement is there was never an original sin !
Braces have always meant multi-line parens* !
And since you can have any expression in parens, we just call a multi-line expression in braces a block
This was done because this is easier to understand coming from languages that distinguish between statements and expressions, like java, python, c and so on

*or more exactly things that should be on multiple lines

Here’s an example of what I mean:

We’re diverting quite a bit from the main subject of this post, if you have any more replies to this I invite you to make a new post (I’ll happily keep chatting about this)

2 Likes

I’ll reply here, but I encourage mods to move this when they have time, not that I think it is not relevant but rather that it’s different enough to not keep engorging this post

I’m sorry if you felt I was dismissive, this is not my aim at all, I felt I identified a misunderstanding and was wrong

Let’s start with the beginning, is this what you mean by the sin ?

I am not in favor of this change. Overall, I think the current whitespace implementation is decent (though not great), but as long as the braces are optional we are moving more and more towards Perl where there are too many ways to do the same thing.

I don’t think linters will solve the problem as it will not work community wide. So, over time, we will get blog posts, books, Stackoverflow answers, libraries etc. that will all have different styles which I think makes the language less approachable and more confusing. Whether Python is popular because or despite of whitespace, at least it is doing it consistently.

On top of that I also think the current proposal has too many warts, such as:

// legal
xs.map x => 
  x + 1 

// illegal
xs.map x => x + 1   // error: braces or parentheses are required

And as mentioned many times before now, currying is quite problematic with the current proposal.

Even if we are not going back to braces entirely, I still think the current proposal has too many flaws and will divide the community even more.

6 Likes

As usual, discussions about indentation become quickly overwhelmingly long, to a point where it is hard to follow the individual arguments. So let me summarize my take-backs so far.

  1. After extensive experience with current whitespace I conclude that it is overall a huge sucess. This holds in particular for optional braces for blocks. There I could never imagine going back to the status before. It holds to lesser degree for definitions in classes and objects. Here I find that we usually need an end marker, so replacing {...} by : and end is less clear of a win. Using indentation for class bodies is mostly beneficial for consistency. On the other hand, blocks dominate class definitions by an order of magnitude, so that’s the most important part.

  2. Given the current class syntax, and continuing the consistency argument, it’s very natural to replace braces by : and newline for function arguments. It tends to lead to aesthetically pleasing code. The argument of syntactic ambiguity with type ascription is a red herring, which has already been debunked by Python, and by the existing Scala experience with : for class bodies, where I have never seen anyone misread this as a type ascription.

  3. Just allowing (2) would mean you have to write map like this:

    xs.map:
      x => x + 1
    

    But we are used to having the parameter at the end. That led to the rule under fewerBraces to also allow

       xs.map x =>
         x + 1
    

    That works OK, but the following variant is more problematic:

    xs.foldLeft(z) (x, y) =>
      x + y
    

    Here it has been noted that the (x, y) look uncomfortably close to being the actual argument of foldLeft. Maybe we should consider the proposal by @Sporarum to also use : in this place. I.e.

    xs.map: x => x + 1
    xs.foldLeft: (x, y) =>
      x + y
    

    I had proposed the same thing before we changed to the current rules. @Sporarum asks why this proposal was abandoned. I believe it was because of the argument that it would be ambiguous wrt type ascription. If someone remembers another reason it would be good to know. But coming back here, I note that technically there is no ambiguity. In a type ascription, the ascribed type must be an InfixType. So, x: A => B is not allowed, you need parenthesis around the type x: (A => B). That means that the => without enclosing parentheses is a sure way to tell that we see a function argument and not a type.

    So that’s an aspect to reconsider. It would be good to get opinions on choosing between the two styles.

Then there are the questions what to do about deeply nested code and curried {...} arguments. I believe it would be perfectly fine to add parentheses or braces in these cases if they help making the grouping clearer. That’s what they are for. Also, maybe in some cases it’s best to avoid such code in the first place. In any case, the most important criteria for me are (1) pleasant code in the normal cases, (2) consistency. Whether the code is pleasant also for edge cases is comparatively less important. Of course it would be nice if that held, but we should compromise neither consistency nor the beauty of normal code for that.

6 Likes

I strongly prefer the style with the :. For the following reasons:

  1. Being able to one-line things without braces is nice. Conversely, not being able to one-line things without braces is a wart, similar to the current “need curlies to use case” in Scala 2. It doesn’t make sense to a user that some arbitrary rule forces braces to be used

  2. As you said, it’s not technically ambiguous. The parser can differentiate.

  3. From a human perspective, I’d argue it’s not subjectively ambiguous either for three reasons:

    1. People rarely ascribe to function types. I don’t think I’ve ever seen that code in the wild
    2. People rarely break function types over multiple lines like that (probably as a consequence of above)
    3. Function types almost always have uppercase identifiers, whereas function arguments have lowercase identifiers
  4. The subjective ambiguity without the colon is much worse: People call methods parens with lowercase identifiers inside all the time! Having another meaning for that, depending on (a) a whitespace that is easily missed and (b) a fat arrow that only comes later seems like a recipe for confusion

Both approaches have some subjective ambiguity, but I’d argue that the version with colons : is far less subjectively ambiguous and overall makes the language much more regular

One issue with the colon : is: do we allow func: arg to happen in one line? It seems we can’t due to ambiguity with type ascription, even though it seems like you should be able to since you can func: x => x+1 and func:\n arg.

But overall that seems like a much smaller edge case than the issues described above, and isn’t enough to tilt the balance away from the colon : syntax being superior, and if we set the covention that colon-block-arg syntax is meant for by-name parameters or lambdas only, I doubt we’d get much confusion. In fact we could even mandate the by-name-param-or-lambda requirement in the compiler and I don’t think there’ll be much objection

7 Likes

I don’t think this is unique to indentation. Basically every single Scala 3 feature discussion has suffered this fate. It is the nature of linear message boards that long threads become overwhelming and die. We have seen all the standard dysfunctions in this thread: interleaved responses, long side arguments, hostile users, spam, and so on.

There is a solution for these issues: threaded discussions, i.e. Reddit or similar. For all its criticisms, large discussions on Reddit are manageable, which is more than can be said about most public forums, including this one. If we care about the quality of these large language design discussions, we should seriously consider trying a new venue that may be more suitable for them

4 Likes

I’m in favor of this, but wouldn’t it entail some Typer action before Parser is done?

No, we could just raise the error later after typing. From a user’s perspective they wouldn’t know the difference: an error is an error, regardless of which compilation phase raises it

It would be good to get opinions on choosing between the two styles.

I’m all for requiring the colon token as in xs.map: x => x + 1 allowing both this one-liner-form and the variant with a new line after colon, so that people can choose how to format their code in relation to readability depending on length of the expressions and if they fit in one line or not.

Not requiring the colon will make it “too silent” in my opinion, as with function application in Haskel, which is so silent that many learners find it confusing and difficult to differentiate between function application and parameters.

3 Likes

But I can live with requiring a newline after colon if I have to :), even if I find it a bit too limiting. This opinion is less strong than my opinion that it should be possible to use a colon as a start for braceless, single argument parameters.

I don’t care so much about how the corner cases of curried nested code is solved, and I’m fine with requiring parens or braces there. This is fine with me:

xs.foldLeft(z): (x, y) =>
  x + y
3 Likes

Overall, I think it’s pretty clear that a broad consensus about the details of what fewerBraces should look like doesn’t really exist. My feelings about the advisability of this aside, that’s probably a strong indicator that promoting it out of snapshot releases after only 2 minor versions would be premature.

Separate discussions about modifications to fewerBraces seem like a good idea, and though I’ll probably recuse myself from those discussions because it’s all equally incomprehensible to me and the ableist subtext of these discussions is frankly unpleasant to engage with, if this is the path Scala is headed down, you might as well do it right - and that means not rushing to publish something when advocates of fewerBraces can’t agree on what it should look like.

2 Likes

Nothing will change even if we continue for 20 more minor versions. We either do it or we don’t.

1 Like

I would hope that by 3.22.x there would be sufficient time to experiment with variations in the implementation that it would converge at a something which a strong majority of the fewerBraces advocates support.

It’s better to get the syntax adjustments done before trying to make it available outside the snapshot releases, and there have been a lot of proposals for syntax changes in this thread, so it seems like there’s still work to do here.

2 Likes

IMHO: It is very difficult to achive consensus when the rules too confused.
I have looked at python grammar(10. Full Grammar specification — Python 3.12.1 documentation).
It seems very clear. There are blocks and expresions.
When I am looking at scala gramma I cannot understad rules.
There are certain points, fortunately.
But it seems every new point increase dificulty non-linearly.
Is there any way to describe grammar more easy, without detaching indentation rules from main syntax?
May be easy documentation of syntax can help to achive consensus.

I think this has to be allowed, if it isn’t ambiguous.

The reason is that refactoring from a one-liner to multi-liner is important.

If I have the braces/parens already, why would I remove them just to go multi-line? It’s an extra hassle. It’s as easy, and less ambiguous, to keep them.

So, that means if we want to be able to write

foo: x =>
  bar(x)

we should allow

foo: x => bar(x)

Note that anything that works on xs.map should work on foo() alone, so I’ve listed that case to highlight the potential for ambiguity.

This is the troublesome one to my eyes:

val bippy = foo: x => f(x)
val quoxo = (foo: x) => g(foo)
val snyth = (foo: x => f(x))

In the first case you have function application; in the second, you have type ascription; in the third you have function application again.

This is why I yet again argue for ... or somesuch to generically open an indentable block. It’s completely unambiguous:

val bippy = foo ... x => f(x)

But, failing anything more adventurous like this, I think requiring the : is better than not.

I won’t likely be using the feature anyway, because of the visual ambiguity, but I think the regularity of having : is probably better. Still, since I’m probably opting out, don’t listen to me.

I do think it’s better to do something than nothing; I do think brace-free is great for short blocks as it is; and I never use the :-ending forms anyway because it’s confusing. Right now, that’s the only way to generate confusion, but if this also provides a way to be confusing, well, I can just not use this one too!

However, the lack of consistency in being able to elide braces is I think moderately worse than the inconsistencies and edge-cases introduced by a proposal like this. So I am in favor of something.

1 Like

We cannot, because of the ambiguity you noted. Also, I think I don’t want a general “apply” operator which : would become if we allowed this. I believe the analogous $ operator in Haskell was a mistake; it is used a lot and makes code less readable for non-experts.

We will need some careful engineering of error messages to correctly diagnose attempts to use : in this way. But that looks doable.

2 Likes