Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow punctuation based alternatives to keywords? #4275

Open
eernstg opened this issue Feb 27, 2025 · 13 comments
Open

Allow punctuation based alternatives to keywords? #4275

eernstg opened this issue Feb 27, 2025 · 13 comments
Labels
brevity A feature whose purpose is to enable concise syntax, typically expressible already in a longer form enhanced-syntax Used with proposals about improvements of the Dart grammar feature Proposed language feature that solves one or more problems

Comments

@eernstg
Copy link
Member

eernstg commented Feb 27, 2025

Dart syntax relies on keywords in many situations. For example:

class C<X extends B<X>> extends S with M<X> implements I, J {}

We could introduce punctuation based alternatives to some of the most frequently used keywords. The rationale would be that this is more concise, and it would very quickly be established as a notation for the given purpose. In other words, extends might arguably be more self-explanatory than :, but it won't take long before they are equally readable if we see them all the time.

One example of a language using punctuation for several of the elements shown above is Kotlin:

// Kotlin.
class C<X : B<X>> : S(), I, J {}

Kotlin uses : to indicate that the following type is the bound of the type variable, and also to indicate that the supertypes of the class are specified (that is, an optional superclass which is extended, and zero or more interfaces which are implemented).

Dart cannot directly use the same syntax for superinterfaces because the superclass can be composite (consisting of a superclass and one or more mixins), and the implemented superinterfaces cannot be recognized by being interfaces (it can be an interface class, but it can also be any other kind of class).

However, we could use two colons (one to replace extends and the next one to replace implements):

// Hypothetical Dart.
class C<X : B<X>> : S & M<X> : I, J {}

If a class implements something and doesn't specify a superclass then we'd have the two colons together: class A :: I {}. Similarly, class A {} means class A : Object {}.

There are many other locations in Dart code where a similar thing could be done. For example, required in a declaration of a named parameter could be written as ! (for example, immediately before the name of the parameter). Also Function in a function type could be abbreviated (perhaps Fun, or maybe a plain F would do).

What do you think? The conciseness can be striking, but the punctuation based notation will surely be considered harder to read by some developers..

@eernstg eernstg added brevity A feature whose purpose is to enable concise syntax, typically expressible already in a longer form enhanced-syntax Used with proposals about improvements of the Dart grammar feature Proposed language feature that solves one or more problems labels Feb 27, 2025
@mateusfccp
Copy link
Contributor

It is only harder to read as long as one doesn't know that it means. Even so, people can still often deduce what it means by context.

I don't think we should refrain from using symbols because someone will find it hard to read. Anyone can learn APL, so using : for extends and & for with is fine.

@Wdestroier
Copy link

Wdestroier commented Feb 27, 2025

Can class A extends B {} and class A implements B {} be distinguished with punctuation?

The S & M syntax may conflict with intersection types if they are ever added in the language.

Edit: I saw the idea to write ::, thank you.

@eernstg
Copy link
Member Author

eernstg commented Feb 27, 2025

Can class A extends B {} and class A implements B {} be distinguished with punctuation?

I'm mainly interested in the overall preferences, I don't claim that anything I mentioned would be a well-designed proposal for actual syntax. If we want to enable this more concise kind of syntax then we'd need to sort out the details, but I'm sure we can do that.

That said, those two declarations would be class A : B {} and class A :: B {}, so they can be distinguished.

The S & M syntax may conflict with intersection types if they are ever added

True, but this might even be a non-problem: Mixin application does create a class which is a subtype of the superclass and also a subtype of the mixin, which makes it yet another kind of intersection type. Next, S & M occurring in the position where a superclass is expected would denote a mixin application, and that wouldn't rule out the possibility that I & J denotes an intersection type if it occurs as the bound of a type variable (which is one place where intersection types and similar entities have been requested several types).

You could of course say that class C : ... : I, J (today: class C extends ... implements I, J) also denotes an intersection type because it makes the enclosing class declaration a subtype of I as well as a subtype of J. However, it doesn't seem possible to use , everywhere to denote intersection-type-ish terms, and also not realistic to use & in implements clauses, so we might well end up with a design where both , and & are used for this kind of purpose.

@lrhn
Copy link
Member

lrhn commented Feb 27, 2025

It'll make class declaration shorter. That's always nice.
They're probably not much harder to read. Would have to do some tests.
The distinction between : Foo and :: Foo might be a little too subtle, but it'd be invisible in DartDoc which just shows superclass and interfaces as a separate list. You don't have to always read the source. If you can read the IDE popup when pointing that the source, you can get a nicer format.

I do feel the pain when writing many class declarations at the same time, but it's not something I do that often.
It's not where the majority of my writing happens, so it's not the first place I'd look for shorthand syntax.
The pain of repeating the class name in the subclass extends or implements clause is at least as big, especially if they're all generic classes. Generally, repeating types is much more cumbersome than the keywords we have today, and this wouldn't help with that at all. It doesn't change how much many tokens you write, it's just a shorter keyword in the same position.

I'm not opposed to the idea on any principled ground, but I don't see it as big win, and therefore not as a high priority. Primary constructors will save much more typing than this, and that's eliminating actual repetitions.

(I'd save more characters total if we allow val or let instead of final for local variables. Or I would hyptothetically, compared to the final that I never write today because it's too long.)

@tatumizer
Copy link

tatumizer commented Feb 27, 2025

I suggest a simpler nomenclature - let's just introduce shortcuts:

  • :: for extends
  • : for implements
  • with - remains as is (rarely used, the shortcut is not justified)
  • val for final
  • no &

let, though a better word than val, has a misfortune of being a verb: let x=0; is fine, but let x; for an uninitialized field or a function parameter is a bit baffling. It's OK for the languages with "types on the right" to declare let x: String, but with the types on the left let String x says it backwards. Maybe it's possible to get used to it though, and read let not as a word, but as a symbol.
It's a philosophical question.

Related: in type arguments, we use the keyword extends though in this context, it doesn't make any difference whether it extends or implements. Maybe :: and : can be made interchangeable inside <...>. Not sure.

@rrousselGit
Copy link

I don't like this.

One of Dart's strength is its simplicity and its similarity to other popular languages. Lots of Dart developers come from JS or Java.
That'd make Dart harder to pick-up for a significant portion of its user-base.

And I doubt we'd be removing the ability to use extends, because that'd be a huge breaking change.
Meaning we'd end-up with two syntaxes for doing the exact same thing. That'd bring confusion, increase the learning curve, and cause pointless stylistic debates like "extends T or : T" where some will use one over the other.

@tatumizer
Copy link

As someone who had a (mis)fortune to code in java for approx. 20 years (before gotten kicked out for good, which ended the torture), I can tell you that the last thing java programmer wants is another java.
The languages supporting the : notation, in aggregate, have a bigger market share than java.
Everyone who knows java will be thrilled to see the remaining javaisms gone.

The additional rationale for a more concise notation comes from the static interfaces: saying
class C extends X Implements Y, Z static implements Q is quite a mouthful, and not readable at all.

@nate-thegrate
Copy link

I know this was kind of just an afterthought, but

Also Function in a function type could be abbreviated (perhaps Fun, or maybe a plain F would do).

I think that's my favorite idea from this proposal :)

@Levi-Lesches
Copy link

Levi-Lesches commented Feb 28, 2025

I think in cases like this, it's important to remember that any given line of code is written basically once but read over and over again.

Of course, one could get used to cryptic punctuation and develop mnemonics that work for them, but I feel it's undeniable that human-speakable words make code drastically more accessible.

It's not even about coming from language X or Y. Python is considered one of the most beginner-friendly languages ever, and it's filled with keywords you won't find in other languages, not to mention abandoning some very common punctuation like ++ and ?: in favor of more explicit keywords or statements. Meanwhile, Rust... I shouldn't even have to finish that sentence, but let's just say its syntax is a genuine stumbling block to many developers and can get in the way of learning the actual concepts Rust is trying to teach.

Dart is already using punctuation a lot, with a large increase since null safety. While it's true that class modifiers are really pushing the boundaries of keyword-to-meaningful-token ratios, it shouldn't be ignored that there was extensive discussion around the exact naming and ordering of those modifiers. Replacing them with punctuation would fly in the face of all meaning associated with those choices. There is nothing about :: that implies "this class extends another's features". I can't even think of a symbol for sealed.

In other words, extends might arguably be more self-explanatory than :, but it won't take long before they are equally readable if we see them all the time.

Again, true, but it's that transition period I'm wondering about. Is it really worth alienating so many just to save some characters when typing? Class declarations aren't expressions, and they don't need to look like it. They currently have a ton of valid combinations, and that's not even including extends, implements, with, or on.

In other words, while it's true that humans can easily get used to some symbols and use them sparingly to save space, there's an extreme gap between rules like "use ? for nullability" and class C<X : B<X>> : S & M<X> : I, J {}

@tatumizer
Copy link

tatumizer commented Feb 28, 2025

@eernstg: I think you made a mistake by presenting the case in an overly abstract form.

// Hypothetical Dart.
class C<X : B<X>> : S & M<X> : I, J {}

Indeed, this can scare off even the most hardcore user. In practice, the components would not be cryptic at all.
Here's a more realistic example (I removed &)

// Hypothetical Dart.
class C<X : num> :Comparable<num> with MyMixin<X> static Decodable<C> {}

There's nothing controversial about the syntax. Compare it with the alternative:

class C<X  extends num> implements Comparable<num> with MyMixin<X> static implements Decodable<C> {}

Does anybody think the latter is more readable?

@Wdestroier
Copy link

Considering the issue title, another case to take into account:

for (var number in numbers) {}

versus

for (var number : numbers) {}

@RohitSaily
Copy link

RohitSaily commented Feb 28, 2025

I also think we could benefit from providing concise alternatives to commonly used syntaxes. I believe concepts that are both (1) widely understood from people coming from all sorts of languages, and (2) frequently occurable would benefit the most from becoming shorter and symbolic e.g. the final keyword could greatly benefit from this. This is because a programmer does not have to learn a new concept, simply a new and very simple notation.

I disagree with the idea that this approach should be avoided because other languages do not do it. This is because it can be a stylistic choice, much of which Dart code already is. Furthermore, Dart has deviated from other common languages in significant and various ways. One example of the class modifier keywords. While there is overlap with different popular languages, looking at the most popular languages all together, it would be incorrect to say these features are common.

I also disagree with the notion that more concise syntax only benefits the writer. Conciseness is also for the reader. By shortening the words and symbols necessary to convey an idea, the reader can (1) take in more information at a glance and (2) have to map semantics to less symbols (less cognitive overhead). Of course it can be overdone, but it can definitely be underdone as well and as of now it is currently underdone given the lack of shorthand syntaxes.

I think conventional mathematics is a great example to follow in this regard. Symbolize simple and widely used concepts while using names for less common ones. Although, I think many symbols are already in use so shortening keywords is primarily the way to.

@Levi-Lesches
Copy link

Levi-Lesches commented Mar 3, 2025

Here's a more realistic example (I removed &)

// Hypothetical Dart.
class C<X : num> :Comparable<num> with MyMixin<X> static Decodable<C> {}

There's nothing controversial about the syntax. Compare it with the alternative:

class C<X extends num> implements Comparable<num> with MyMixin<X> static implements Decodable<C> {}

I mean, it starts with the very simple : = extends, but then

  • :Comparable somehow equals implements Comparable, even though whitespace doesn't usually matter in Dart
  • static implements becomes just static

If the question here is to just make : = extends, or : = in, that's one thing, but asking for an overhaul of the existing syntax is an entirely different thing, and while I can get behind static instead of static implements, I don't know how valuable it is to overload punctuation that has little to none intuitive meaning as opposed to using real human words.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
brevity A feature whose purpose is to enable concise syntax, typically expressible already in a longer form enhanced-syntax Used with proposals about improvements of the Dart grammar feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

9 participants