Make "fewerBraces" available outside snapshot releases

I agree this is a mess because the .toSet are dangling in the braceless variant. I opine that this mess started with what I think was an incorrect design decision for braced block as function parameters when a lambda and not a Unit.

I would have preferred instead (which if had been done consistently then a bijection between braces and blocks would have enable automatic tooling to help those who can’t read braceless):

    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

When there is chain of method calls, I argue the parentheses should be required because otherwise we’re repurposing braces to act as parentheses which is how Scala becomes so darn confusing with so many special cases. Please stop trying to be cute. This was the mistake Rust made also. Be regular in syntax and usage if you want to repair the reputation of Scala as an unnecessarily complicated language:

    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

In which case we can drop the braces, so the utility of the braceless style returns. Tada problem solved!

    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

I never have liked much Scala’s abstruse Haskell-like invocation of functions with spaces instead of parentheses. The feature can be useful rarely but I think should be discouraged in braceless style if we are indeed targeting the onboarding of junior programmers from Python. Function invocation with parentheses is more well understood. Okay there can be exceptions where it’s elegant to not employ paratheses so let’s not throw the baby out with the bathwater. But don’t get going in certain direction of attempting to minimize everything and be so cute that you tie yourself in knots and confuse the heck out of newbies. In short lean to regular syntax and usage please.

Due to unfortunate timing, I’ve not been able to receive a lot of feedback on my proposal to make fewerBraces more regular

In the meantime, I looked more into it, and it seems like it was originally like that, is this the case ?
If yes, why was it changed ?

And more generally, what do you think about my proposal ?

Why are you against not just using the => as legal at the end of a line that opens a new braceless block where a lambda is accepted at that position?

It’s somewhat irregular in the sense that it’s not as general as a rule that a colon admits that the rest of the line will join with a braceless block that follows. But it’s easier on the eyes and I have read that some people (e.g. dyslectics and those accustomed to colon as a delimiter for terms and definitions) get distracted by the colon. The colon preceding the lambda parameters is arguably symbol soup-ish? I liked the colon idea because it also enables for example a shorthand ternary with a colon in place of the then, but I am not sure I like that benefit more than I dislike the symbol soupness of the colon in front the lambda.

Were there other benefits of your proposal? Does your proposal enable the single line lambda without parentheses?

The thing is, I’m also dyslexic :sweat_smile:, and for me at least:

xs.map x =>
  ...
// reads as:
(xs.map x) =>
// (Of course, this makes no sense, but it's not the logical part that does the intuitive reading)
  ...

The reason being that => is a “strong separator”, whereas there’s no separator between map and x, by adding :, we explicitly add a separator, which makes the parsing way easier (for me):

xs.map: x =>
  ...

(I have the same issue with the extension syntax, hence why I proposed E6 and E7)

Of course the main problem is that : already has two really distinct meanings, one in typing, and the other in introducing “bodies”, so there’s only two options:

  1. Keep this ambiguity and adapt as much as possible (imo this spells trouble in the long term)
  2. Remove this ambiguity by using two different symbols (or potentially keywords, but I’m not in favor)

Finally, the if question, I’m strongly against this:

if c: a else b
//parsing as
if c {a} else b
//instead of (even tho this makes "no sense")
if c {a else b}
  1. The then is already a perfectly fine and flexible solution
  2. This would be an exception to my description of colon-bloc syntax, as we now need to lookahead to know where the bloc ends (see below)
  3. The above makes it harder to develop parsers for scala which affect both the compiler, and even more importantly tooling (for example a simple regex* is no longer sufficient to swap between syntaxes)
// cannot simply go forward until finding the first "else"
if c1: if c2: e1 else e2 else e3

*it would need to know about indents, but the language is regular modulo that

Yes, my proposal allows single line lambda without parentheses, see P2, E1

The other very important advantage is that it can become the rule for all braceless syntax with a very simple tweak:
"Braces can be dropped if

  1. the bloc is correctly indented, and
  2. the opening brace follows a keyword, or is replaced by :

"

(There’s probably cases I’m missing, do point them out)

3 Likes

Wouldn’t this be parsed as an attempt to cast c to the type a?

1 Like

This is sadly an issue, but even with:

xs.map: x => x
// identity function or cast to x => x

This is why I would encourage us to 1) move past the : symbol to introduce blocs, and 2) discourage braceless lambdas, as braced are clear enough (in my opinion, except for DSTs)

2 Likes

After 5 months of almost exclusively using Scala 3 (including a lot of porting of Scala 2 code to more idiomatic-ish Scala 3), it still feels like that to me every single time. I guess I’ll learn it eventually but after five months it feels no less like than than when I started. Caveat–because it always makes me do a double-take, I have used it sparingly in code I write; my exposure is almost always as a reader not a writer. I would learn faster with immersion and practice, one would think.

But it’s the same thing with given. Every single time I want to write given where I am supposed to write using. (Clarification: I usually don’t write given, I just think it. Then I think, “no, wait, using”, and type that. But if I am ever uncautious, it comes out as given.) Being given something is the standard in mathematics: “given x >= 0, the square root of x is the number y such that y*y == x”. After five months of extensive use it still gets me every time. Maybe : isn’t as hard to adjust to, but this gives me little hope for : being better for me personally (and is kind of an unpleasant commentary about my plasticity for unlearning).

I love Scala 3 and view it as a big improvement on Scala 2, but alas, I buy the improvement with considerable pain. (All foreseen, too. I may be atypical, but, annoyingly, I was 100% correct with what I thought I’d find problematic. I like Scala 3 considerably more than I’d anticipated mostly because the things I thought I’d like a little I actually like a lot. But the things I view as a problem were a problem–so this is at least anecdotal evidence that we should all take seriously, at first at least, anyone’s claim that “this will be hard for me”.)

Let’s please be careful not to make it worse with :?

Question for @Sporarum – how does this read to you?

xs map x =>
  foo(x)

to me it feels less weird than xs.map x =>.

But mostly I use these things in chains anyway, so usually the .map would go on the next line which…well…does that solve the issue for you?

xs
  .map x =>
    foo(x)
3 Likes

Your issue is yet another case of the pitfalls of not using parentheses to invoke functions. Your issue is entirely solved by employing parentheses. I argue that parentheses should be used by those who want to write highly readable code in the braceless style (for braced they can do otherwise). Otherwise they can employ the colon-less syntax of “fewerBraces” to be cute and eliminate some symbols at the cost of making their code harder to read for some people. This is why I have always been against the liberal usage of spaces between function parameters in lieu of a parenthesized, comma-delimited list. I would prefer to be explicit, which is strange because I also appreciate the aesthetic beauty of minimizing symbol soup (which is one reason I like the braceless style). However when the parameter(s) are blocks, that ending parenthesis is a hobo-pendage, so I have some affinity to the space delimited style. The arrow => should cue you into that it must be preceded by a parameter list, so there really isn’t any ambiguity.

Employing (i.e. overloading the functionality of) curly braces instead (of parentheses) as parameters to functions has ostensibly ruined the bijection that we could have used to alleviate the tension about this braceless versus braced bifurcation in coding styles. Sad state-of-affairs. We need to be more careful and less cavalier in design of syntax because we now see the knots we tie ourselves in if we don’t apply a lot of careful forethought.

I guess I agree with you now to prefer then over the colon for the ternary.

I realize now I don’t like the colon for introducing a new block at all. That defeats the point of braceless intending to reduce symbol clutter. And the colon is not even consistently employed (yet another adding special case complexity to Scala…when will they ever learn to stop that!). It is not needed at all for class definitions and it is confusing for prefixing lambdas, where we already have a syntax solution with parentheses. Parentheses add more symbols, but it’s a standard syntax that has been around since C was invented. Spaces delimited parameter list is weird for those of who started coding with C in the late 1970s. I will be able to teach myself to read either syntax. We can adjust to a pattern with repetition.

I agree the colon for introducing blocks is inconsistently applied and any attempt to generalize its use will be confusing as explained above. Thus the colon should be dropped entirely because Scala needs to stop introducing special case syntax. Will they ever learn to stop doing that and be more regular in their syntax design discipline? You would think after all the years of feedback about Scala having too many idiosyncrasies and corner cases (including in the syntax) that they would heed the feedback.

Also I agree about given as a poor choice for the implementation of a trait. Ostensibly the intention was to sound sophisticated like mathematicians. Yet another example of “being too clever.” Okay I understand type classes are an abstract algebra, but still we’re just humans so keep it simple anyway.

Why not simply implements? Who could ever misinterpret that? Or I will be copacetic with retaining implicit if that doesn’t cause any conflict.

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?