Feedback sought: Optional Braces

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

Is it possible to have the language be non-regular :, with, etc., while applying auto-fixes in IDEs for common mistakes (like text editors can automatically capitalize the first character after a period)?
For example, if someone writes while true: then it will be automatically converted to while true do. With such a feature I think the irregularity problems are mitigated significantly.

3 Likes

Stylistically, I think single-method extensions should be in one line (at least the definition and = operator, if not the actual implementation), and think that’s enough of a visual cue to warrant mandating with for collective extensions.

The use of the with keyword is a good visual cue that multiple methods are expected:

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

Indeed, IDEs will probably reformat and delete with as a suggestion, if you use collective extensions with a single method; and certainly I would like such matters of syntactic consistency to be handled by scalafmt at some later date.

8 Likes

The purpose of : (and now with) in class/object/trait is to disambiguate in case of stray whitespaces. In extensions you don’t have the problem, so… against with in extensions.

It would be possibly auto-fixable but a pain to learn and use.

I really prefer to just have a more regular language.

1 Like

The compiler might not need it, but if it helps to make the language more regular (and thus predictable for the user) I’m all for it.

I want to be able to simply write code without having to think about specific syntax intricacies.

4 Likes

People who liked : generally didn’t like ... But although I was an early proponent of it, I think other changes have made it unnecessary. I do like .. better than :, but I think the most important consideration is this one:

Hence, with wins, because it’s consistent with the pre-existing idea of with. .. could be self-consistent, but it’s an entirely new thing: a “begin significant indentation mark”, as opposed to “optional braces”. The logic is “add this new thing to get a new syntax” rather than merely “you can leave off braces when they’re superfluous”.

So :+1: from me for with.

5 Likes

For completeness,

class C[A](x: A) extends T w/
  def f = ???

w/ means with. If it is a soft token at EOL, then the slash looks line-continuing.

2 Likes

There’s also the question whether with and braces together is supported:

class C[A](x: A) extends T with {
  def f = ???
}

It can be seen as optional with in addition to optional braces. Then there’s the question of given:

given C with {}
given C {} //  `with` elided
given c: C {} // named
given c: C & {} // abstract with refinement

Of course you cannot omit both.

.. might make a comeback later. Remember one of the proposed extensions after 3.0 is to allow optional braces after operators, and to have an operator for function application. So far we have “left pipe” <| as the leading candidate for that apply operator, but .. could be an alternative.

2 Likes

It’s supported. As to givens, the rule will be that the type of a given has to be a simple type, possibly with annotations. This means that

given c: C & { ... }

is illegal, but

given c: (C { ... })

will work and will define an abstract given with a refinement.

The complete syntax is as follows:

GivenDef          ::=  [GivenSig] (AnnotType [‘=’ Expr] | ConstrApps TemplateBody)
GivenSig          ::=  [id] [DefTypeParamClause] {UsingParamClause} ‘:’ 

AnnotType         ::=  SimpleType {Annotation}  

SimpleType        ::=  SimpleLiteral          
                    |  ‘?’ TypeBounds
                    |  SimpleType1
SimpleType1       ::=  id                             
                    |  Singleton ‘.’ id       
                    |  Singleton ‘.’ ‘type’    
                    |  ‘(’ Types ‘)’          
                    |  Refinement                            
                    |  ‘$’ ‘{’ Block ‘}’
                    |  SimpleType1 TypeArgs       
                    |  SimpleType1 ‘#’ id          

Fair enough to select with over : if you prioritize regularity over ergonomics + esthetics in this case. I can live with that, and learners should be ok (even if with is wordy).

Now I need to get hold of M4 asap to re-program my fingers and learn how to love this new syntax :slight_smile:.

Competing requirements need to be balanced and trade-offs are seldom perfect…

Thanks for your prestige-less attitude in the quest for finding good solutions to conflicting goals! :heart_decoration:

5 Likes

BTW: When is M4 out?