Feedback sought: Optional Braces

Here is a method from ExtractSemanticDB, but using 4 spaces to indent. 4 spaces better?

// 4 spaces indent
private def symbolKinds(tree: NamedDefTree)(using Context): Set[SymbolKind] =
    if tree.symbol.isSelfSym then
        Set.empty
    else
        val symkinds = mutable.HashSet.empty[SymbolKind]
        tree match
        case tree: ValDef =>
            if !tree.symbol.is(Param) then
                symkinds += (if tree.mods is Mutable then SymbolKind.Var else SymbolKind.Val)
            if tree.rhs.isEmpty && !tree.symbol.isOneOf(TermParam | CaseAccessor | ParamAccessor) then
                symkinds += SymbolKind.Abstract
        case tree: DefDef =>
            if tree.isSetterDef then
                symkinds += SymbolKind.Setter
            else if tree.rhs.isEmpty then
                symkinds += SymbolKind.Abstract
        case tree: Bind =>
            symkinds += SymbolKind.Val
        case _ =>
            symkinds.toSet

// 2 spaces indent
private def symbolKinds(tree: NamedDefTree)(using Context): Set[SymbolKind] =
  if tree.symbol.isSelfSym then
    Set.empty
  else
    val symkinds = mutable.HashSet.empty[SymbolKind]
    tree match
    case tree: ValDef =>
      if !tree.symbol.is(Param) then
        symkinds += (if tree.mods is Mutable then SymbolKind.Var else SymbolKind.Val)
      if tree.rhs.isEmpty && !tree.symbol.isOneOf(TermParam | CaseAccessor | ParamAccessor) then
        symkinds += SymbolKind.Abstract
    case tree: DefDef =>
      if tree.isSetterDef then
        symkinds += SymbolKind.Setter
      else if tree.rhs.isEmpty then
        symkinds += SymbolKind.Abstract
    case tree: Bind =>
      symkinds += SymbolKind.Val
    case _ =>
    symkinds.toSet

It is an interesting comparison. The 2 extra spaces seem to give the code room to breathe and relax :slight_smile:

Has :: been tried as end of line indentation marker for templates? :: is easier to see than : .
@lchoran examples using :: and 4 spaces.

Example #1

class TierTwo(
    now: Instant,
    one: Path, two: Path, net: Path,
    parallelism: Int, stopBy: Instant,
    quiet: Boolean = false,
    policies: Tiering.BackupPolicies = Tiering.BackupPolicies.default,
    parameters: RunMwt.Parameters = RunMwt.Parameters.default
)::
    val outer = this
    import Tiering._
    ...

Example #2

    class Zip(home: Path) extends Three(home) with Archived::
        private[this] def zip = this  
        type S = Record.Three.Zip[this.type]
        val creator = new InPlateSlot[S, zip.type]::
            val owner = zip
            protected def uncheckedCreate(home: Path, sd: SpannerDir): S =
                new Record.Three.Zip[zip.type](zip, home, sd)
                protected val decider = (s: String) => Yes(s == "zip")

    object Zip::
        def apply(home: Path) = new Zip(home)
        def old(home: Path) = new OldZip(home)
        class OldZip(home: Path) extends Zip(home)::
            override def versioning = Versioning.OhOneA

Example #3

    val (contested, uncontested) = unsaved
        .map::
            case (f, t, _, Some(sf)) => No((f, t, sf))
            case (f, t, c, _) =>
                val g = targetFrom(c, remote) \: (f % "zip").getName
                if safe{ g.exists }.yesOr(_ => false) then 
                    No((f, t, g))
                else
                    val ga = targetFrom(c, remote) \: (f % "zip.atomic").getName
                    if safe{ ga.exists }.yesOr(_ => false) then
                        No((f, t, ga))
                    else 
                        Yes((f, t, c))
        .unmix  

I concur. Beside lambdas (SAM) there are also anonymous classes in general (MAM, for multiple abstract methods):

def addKeyListener(listener: KeyListener) = ...

addKeyListener(new KeyListener {...})

So I don’t really see a way out of only-partially-optional braces here.

As an aside, constructor inference would allow this nice form:

addKeyListener(new {...})
1 Like

@odersky I have attended Scala 3 for Scala 2 developers by @jdegoes - I was a bit hesitent first wrt. optional braces, but after using it for a while I think it is great! Btw, I am very excited about all the changes/additions in Scala 3, thank you for all of your hard work!

1 Like

I’d like to double @regiskuckaertz’s suggestion for the wider usage of =. I think it’s generally intuitive as a marker of a definition and doesn’t appear as an ambiguous semantic overload.

But I think it was considered before and there were some issues with it. I can’t find it now. Does anyone remember or know why this won’t fly?

Generally I’m all for the optional braces and will be happy to use it. It would be nice though to figure out these details because so far it seems there is no perfect solution.

2 Likes

Wouldn’t this conflict with the :: constructor (which is used pretty frequently). What if you have something like this? It’s a bit ambiguous.

foo.bar ::
  longExpressionThatNeedsToBeOnItsOwnLine
2 Likes

This amazing thread is like certain fat books on the shelf since college that I always meant to read once I didn’t have to do it for coursework.

I hope it doesn’t dissuade anyone from asking for feedback in the future.

Someone joked about the em dash, perhaps on twitter, which I started reading again recently. I was going to joke about preferring em dash to double dot. That made me think more seriously about how it’s too bad that Unicode symbols are foreclosed. For example, even Unicode right arrow was not sustainable.

I see there is a 3-dot vertical ellipsis⋮

which has the punning semantics mentioned in this thread for ordinary colon.

Not only is my “source file” composed of “text”, it has to be “ASCII” (for all intents and purposes).

If a colon is an equals sign of zero extent, perhaps there are signs of varying extent.

My other passing thought was that the discussion has been about a continuation symbol, something to insert. The alternative is to assume continuation and require a semicolon or blank line if continuation is not desired.

Other passing thoughts include: This is a good time to revive my proposal for line comment opened by double-semis:

;; this is a comment
xml // attr ;; this is an operator

I just remembered to mark it “plain” so it renders better as hypothetical syntax.

I would be willing to contribute to the FAQ, “Why doesn’t for (;;) mean forever?”

2 Likes

This thread is kind of a special case, as it’s at least a year’s worth of backed-up feedback, on a change that people really care about. Most feedback threads aren’t nearly this long :slightly_smiling_face:

1 Like

Do we really need the option of not needing braces, ever? Sure, it’s nice to not use them for single line methods that later turn into multiple lines, and perhaps I could get used to some variant of -Yindent-colon, but when calling a method taking multiple function arguments, I can’t think of any way significant indentation would make it more readable.

foo { x =>
   //do some stuff
   //do more stuff
} { (a, b) =>
  a.fold(b)(bar)
} {
  println("Braces aren't always bad.")
}
//or
baz({ x =>
   //do some stuff
   //do more stuff
}, { (a, b) =>
  a.fold(b)(bar)
}, {
  println("Braces aren't always bad.")
})

versus

foo:
  x =>
     //do some stuff
     //do more stuff
:
  (a, b) =>
    a.fold(b)(bar)
:
  println("This just looks weird.")
//and
baz(
  do 
    x =>
     //do some stuff
     //do more stuff
, do
    (a, b) =>
      a.fold(b)(bar)
, do
    println("I may be misinterpreting this syntax, though.")
)

It’s not particularly elegant-looking code, but :, do, @, .., etc. would just mess it up more. Do we really care about being able to completely eliminate braces if one wants to? It would be a lot easier to design this to satisfy everyone if the crazier cases don’t have to be supported.

5 Likes

I always think in these cases of curried parameters and lambdas spreading over multiple lines the lambdas should be pulled out into methods and passed by their identifier, (even with brace syntax) its just more pretty for me to read

4 Likes

In my opinion, based on Python and Smalltalk, the best way to handle multiple arguments in the absence of parenthesis is to use named arguments. For example,

foo
  a: 1
  b:
    // do something
    // do some more
1 Like

Of course, in the specific case of Scala it would be a = and b =. I used colon in the example because that’s what these other languages use.

For me it depends a lot on the particulars, a 3-line lambda isn’t worth pulling out into a named method for me because the additional indirection needed to find the body doesn’t pay for itself (of course YMMV)

1 Like

After reading the discussion i don’t see a single point in favor of introducing this (except that it’s not as bad as Python).
All i see is valid points about how it will complicate editing the code and a bunch of kludges to overcome them.
My personal experience is that you can’t use it without proper support from editor.

4 Likes

I would be fine with a requirement that all multi-line arguments should be named.

But I still would want one unique block start marker that is for every block and for nothing else.

How about all the people above who expressed their positive feedback on the feature, saying this made their code and coding experience better?

I’ve been using it in VSCode just fine (without dedicated editor support). Is your bad experience related to the copy-pasting problem? All it takes is a couple of TAB or SHIFT + TAB hits after pasting. Maybe it’s less ergonomic than auto-formatting right after pasting (though I never do that, so I wouldn’t know), but saying that this minor hitch means “you can’t use it” seems like an exaggeration.

2 Likes

It is quite interesting that the positive feedback is quite bigger here, but nevertheless significant indentation is rather old idea and it does not seems overwhelming in the world at all. I cannot see how current improvements change situation completely.

I have relaxed a little only because it is optional and Odersky has guaranteed it will be so in the future.

1 Like

Sorry for the long post.

I took a look at the longer Scala 3 examples, and also the Python code.

By and large I found the Scala code to be okay to read. I think that it is probably better than having brackets everywhere, but subjectively not as clear as it could be. I.e. I would still choose to use {} and () is more places because I think that it would make the code more readable.

There were a couple of cases where the code is laid out in a way that I really dislike (sorry):

I find this code really jarring where the second clause of the condition is indented to the same level as the if:

      if !excludeDef(tree.pid.symbol)
      && tree.pid.span.hasLength then
        tree.pid match

I think that I find the conditional case statements to be equally jarring:

      case tree: (DefDef | ValDef)
      if tree.symbol.isSyntheticWithIdent =>
        tree match

Generally, in terms of the indentation based syntax:

I find the use of keywords to sometimes introduce blocks and colons in other cases to be inconsistent. I also have some concern that colon is used both to ascribe a type and to introduce a block, although it does seem to me that using colon is a pragmatic choice, given that it already used in that way in other languages (e.g., Python).

What I find stranger is that in some ways the Scala code looks much closer to Python except that Python uses : rather than then or do. I.e. one of the most frequent constructs is arguably more verbose than before. Having said that I really do like the ability to remove the unnecessary brackets in many if conditions.

There are some cases where brackets have been removed, but personally I think brackets make the code clearer. E.g. the top level class/object definitions and anonymous classes. E.g. I would probably have used brackets for the following code because I just find it more intuitive:

private val symsAtOffset = new mutable.HashMap[Int, Set[Symbol]]():
  override def default(key: Int) = Set[Symbol]()

One suggestion for a potentially way to solve the inconsistency on block marker would be to always have keywords available to introduce blocks, but also allow colon to be used in place of any of those keywords. E.g., allow

if cond then
  stuff
else
  more stuff

And also

if cond:
  stuff
else:
  more stuff

For classes, it could be:

class Foo where
  def ...

Or

class Foo:
  def ...

But perhaps having a third way of introducing blocks is too much …

Overall, my main concern is that I’m not sure whether the Scala 3 indentation syntax has had enough time and community usage to really be sure that it is right and doesn’t need to change again. As mentioned previously, I would prefer if it is marked as an experimental feature that could be changed or be refined based on community feedback and experience. Even if it is an experimental feature then I do not mind if it is enabled by default, since I suspect that it won’t be possible to get enough feedback without it.

My expectation is that I will structure my code to use a combination of brackets (e.g. top level classes) and longer methods, and then use the quieter syntax for shorter methods. I’m also still not sure about the end statement. I can see that in some cases it would also be helpful to annotate what is being ended (e.g., a class). However, when used for method bodies, I think that I would probably prefer to see it indented to the same level as the method statements rather than at the same indentation level as the def statement. I suspect that in all other cases I would naturally have it at the same indentation level as construct that it is ending, i.e., I’m being inconsistent. But I’m really not sure whether I will use the end statement at all, or just use brackets in those cases where methods are long enough to need an end marker.

But I really do appreciate the efforts that Martin and others have gone to try and improve the Scala language syntax both making it more pleasurable to read/write and hopefully better for newcomers. And so far, Scala 3 looks like an exciting new version of the Scala language.

8 Likes

I like indentation-based syntax but feel some things need adressing:

  • 4 space indentation
    I find that four spaces really helps me scan over code faster. Even with visual tools like indentation rulers it looks better, as the rulers aren’t as tightly packed together. It also aligns very nicely with def/var/val
  • Colons
    I often get confused as to when I am supposed to declare a block start with colon (the ones needed after class/object/given?/extension?/other things I might’ve forgotten?. I accidentally add them for while-statements (something about do makes me add a colon after it).
    Python gets around this by always using colon to signify block start and it would make sense to have something similar for Scala. Equals sign seems like a contender as it’s already used for method/value one-line definitions in Scala 2.
2 Likes

How about 3 spaces? Best of both worlds? Can we make it happen?

2 Likes