Feedback sought: Optional Braces

D’oh, I didn’t realize you were Martin Odersky! I see, that’s good news.

2 Likes

I read class Foo extends Bar with as defclass Foo extends Bar with , so seems great.
,

It seems less comfortable for anonymous classes.

I just want to weight in supporting regularity in general.

I recently played with braceless syntax and got tripped up a few times and immediately went back to just using braces. The idea that sometimes we would use :, sometimes with and sometimes nothing at all, IMO, should be a non-starter.

I also worry about ambiguity in teaching if not strictly in parsing that using : would create (is it type ascription or is it a block).

So, I’m very glad to read this latest iteration.

Finally… I think the fact that this is all still massively in flux should encourage us to pump the brakes on the idea that this should also immediately be the preferred syntax. If it is still significantly changing at this date, what assurance do we have that we won’t discover more serious problems, especially as people use it more. I personally think braceless syntax should be considered an experimental feature still subject to change in 3.1 and 3.2.

20 Likes

:point_up: so much this.

Maybe I’ve missed some motivations in the rest of the thread, but while including this syntax in the first 3.0 is a ship that’s sailed, we’re still on time for making it an experimental feature, which would allow gathering more experience about it and refining it further.

The fact it’s changing so often so close to the final release is empirical evidence that it’s not mature enough to the preferred syntax for Scala (yet!)

10 Likes

Since this thread is active again, I’d like to mention that:

has recently been fixed by https://github.com/lampepfl/dotty/pull/10969 (thanks @odersky!), both syntaxes now work.

If I’m not mistaken, that means the only remaining case where braces are currently required is the partial function literal syntax:

List(1, "a").collect({ case x: Int => x })
List(1, "a").collect { case x: Int => x }

val f: PartialFunction[Any, Int] = {
  case x: Int => 1
  case x: String => String
}

I suggest simply dropping the braces:

List(1, "a").collect(case x: Int => x) // now there's only one way to write this
val f: PartialFunction[Any, Int] =
  case x: Int => 1
  case x: String => String

This accomplishes two things:

  • we can declare victory in the War Against Braces without having to enable -Yindent-colons (or one of the alternatives discussed in this thread, which might or might not get in eventually).
  • There’s now exactly one canonical way to call a non-infix method: a.f(x), this is a big win for consistency (and IMO a reason not to proceed with -Yindent-colons or one of its alternatives, but that’s a debate that can be had post-3.0).
11 Likes

I would be very glad to see the : syntax be abandoned in favor of with.

Last time I wrote anything serious in Python was 8 years ago, and I still get tripped up regularly by the : syntax for indentation in my Scala 3 project. I caught myself writing things like this several times when I was distracted:

if x > y:
  blah...

Though it should be noted that I’ve also been using the experimental -Yindent-colons option for : in expression, which may have contributed to my mistakes. I’d also like to see that syntax change for us to experiment with the replacement sooner than later.

I would really like to have an alternative to braces OR parens to delimit blocks in expressions. Both are a nuisance, and replacing one with the other is not a total victory in my book. This especially sticks out like a sore thumb because we can already get rid of braces for control structures.

I’d like to be able to write code like this:

  case Match(scrut, branches) =>
    val s_ty = typeExpr(scrut)
    val (pats, funs, rets) = branches
      .map:
        case AST(Fun(l, r), c) =>
          given Ctx = ctx.nest
          val l_ty = typePattern(l)
          val r_ty = typeExpr(r)
          (l_ty.payload, AST(Fun(l_ty, r_ty), c), r_ty.payload)
      .unzip3
    ...

or like that:

  var changed = true
  while changed do
    changed = false
    varSubst.mapValuesInPlace:
      (k, v) => v.flatMap:
        tv => varSubst.get(tv) match
          case Some(x) => changed = true; x
          case None    => v

Having to break such beautifully-flowing code with parens on empty lines would really be a shame.

3 Likes

I also don’t think a consensus was reached in the number of spaces for indentation. IIUC, the current implementation is 3 spaces, but this is highly irregular although it has other nice properties.

2 Likes

Yes, I agree. We will not be able to mandate that, nor do we want to. Number of spaces is not defined in the language and is orthogonal to everything else. If there is a standard it’s formed organically.

1 Like

True, although official examples, books, style guides and formatters do matter in this regard, so I personally try to be conservative there.

1 Like

Thank you for continuing to iterate on this and seek input.

I’m definitely in favour of ‘with’ over ‘:’. Keeping ‘:’ for type ascriptions makes the language cleaner and more regular.

For consistency, I do think that it would be good for the language to also allow brackets or ‘with’ to be used when collective extension statements are defined. Although perhaps that is already allowed in the latest version?

But for most class/object definitions I still think that the best symbols are probably ‘{’ and ‘}’. I guess the one benefit of ‘with’ is being able to name the end of the class, but a decent editor should be able to provide that information anyway.

For short method bodies and control statements I can easily see the benefit of being able of being able to elide the brackets to make the code shorter and cleaner. But for much bigger/longer constructs the appeal isn’t so obvious to me. Once Scala 3.0 is out (and fastparse is available) I will move my code to Scala 3 and try out the different choices, and pick the syntax that I prefer.

I do strongly think that we should keep this experimental for a release or two - but I don’t think that means that it needs to be under an experimental language flag. This would mean that the updated Scala books might not get written until 3.1, but on the plus side it is possible to get real feedback about how Scala 3 is being received and used by the wider community before the syntax ends up being fixed in stone for the next 10 years.

It would be nice to do this in one release, but I still think that it is more important to be absolutely confident that all of the right choices are being made here.

2 Likes

Just as a call-back: Back when the discussion between : and with was in full swing further upthread, .. seemed to break the stalemate for many people (though not all). It’s short, unobtrusive and scores perhaps more points than either : or with.

1 Like

About collective extension methods, the situation is somewhat different.

  • collective extension methods do not define a local scope, where as with + <indent> means “local scope” everywhere else.

  • we certainly do want to allow a single extension method without needing with. I.e.

    extension (x: T) def append (y: T): T = ...
    
    extension (x: T)
      def prepend (y: T): T = ...
    

    should both be accepted. So then when we combine the two to a single collective extension, would we require a with or allow one? Requiring it is just as bad as braces in that I have to add something to the other scope if I have more than one thing in the inner scope. Allowing it just gives two ways to do the same thing.

That’s why I think it’s better not to use with. But there can be a nice error message that explains the matter if somebody writes it.

1 Like

My impression was that opinions of .. were much more polarized than for the other alternatives. Some people liked it, but the people who didn’t like it really didn’t like it.

I welcome the change to with, i find anywhere where we have class parameters the colon starts to look very sketchy.

-enum Tagged[T](val cls: Class[?]):
+enum Tagged[T](val cls: Class[?]) with
   case IntTag  extends Tagged[Int](classOf[Int])
   case UnitTag extends Tagged[Unit](classOf[Unit])
4 Likes

For clarity. are collective extensions allowed to use braces?

The current documentation suggests that they are:
https://dotty.epfl.ch/docs/reference/contextual/extension-methods.html

So, I was coming more from the angle that “with” is allowed instead of a pair of braces rather than it introducing a “local scope”.

Yes. So in that analogy, with makes sense. Or rather, braces don’t really make sense here since they pretend to introduce a local scope where there is none. But it’s the only thing we have if we do not want to rely on indentation. I think it’s OK. Code using extension methods is always new code, so people will tend to rely on indentation, even if they don’t use braces elsewhere.

I guess that if you don’t allow ‘with’ now for collective extension statements, then it doesn’t close the door to allowing it in future if, after more experience and feedback, that was deemed to make the language more regular.

For what it’s worth, having followed this conversation for a long time, my personal preference is definitely with.
And, for consistency, I think it should be required for multi-line enstensions.

Keep in mind that IDEs can choose how to display keywords, so for those who find it cumbersome/distracting to watch, I think there’ll be ample opportunity for IDE configuration.

10 Likes