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

Replace current "@required this.name" in the named parameters with "this.name!" #1103

Closed
manyopensource opened this issue Jul 20, 2020 · 11 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@manyopensource
Copy link

manyopensource commented Jul 20, 2020

This features is simple, understandable and requires less time to make changes!

Instead of this

class Message {
  String id;
  String content;

  Message({@required this.id, @required this.content});
}

We will have this

class Message {
  String id;
  String content;

  Message({this.id!, this.content!});
}

Thanks!

@manyopensource manyopensource added the feature Proposed language feature that solves one or more problems label Jul 20, 2020
@mateusfccp
Copy link
Contributor

The current @required is a annotation, not a keyword, so it is more associated with the meta library and the linter than with the language.

In nnbd it won't be a annotation anymore, but a real keyword instead (required).

Anyway, I think ! will cause confusion, specially considering that it is a operator already used when you want to tell the compiler to ignore some null-safety checkings.

@lrhn
Copy link
Member

lrhn commented Jul 21, 2020

The required is for any named parameter, not just initializing formals (this.something in a constructor parameter list).

Replacing the required prefix with a ! suffix is unlikely.
This use of the syntax means something different from suffix ! in other places (non-null assertion). Also, it's easy to overlook and works badly with nullable function parameters like:

foo({int someFunction(int)?!}) => ...

(Granted, nullable required parameters are probably going to be rare enough that ?! would be the proper exclamation when seeing them).

So, what would it look like otherwise?

void foo({required int bar, required Map<String, String>? baz, required int qux(int value)}) => ...
//vs
void foo({int bar!, Map<String, String>? baz!, int qux(int value)!}) => ...

Neither is particularly readable. I think I'd prefer a prefix in general, because suffixes are too easy to miss.
Even prefix ! works better for me:

void foo({int !bar, Map<String, String>? !baz, int !qux(int value)}) => ...
// or
void foo({!int bar, !Map<String, String>? baz, !int qux(int value)}) => ...

(the former more than the latter).

There is definitely room for improvement in Dart parameter syntax. I'd personally prefer a complete overhaul to just twiddling with a single word.

@manyopensource
Copy link
Author

manyopensource commented Jul 21, 2020

@lrhn

void foo({@required int bar, @required Map<String, String>? baz, @required int qux(int value)}) => ...
//vs
void foo({int! bar, Map<String, String>?! baz, int! qux(int value)}) => ...
//or
void foo({int! bar, Map<String, String>! baz, int! qux(int value)}) => ...

It's not me who invented question mark "suffixes" (for nullability purposes), I'm just using the same "API" to replace the meta-tag "@required"

@lrhn
Copy link
Member

lrhn commented Jul 21, 2020

Being required is not part of the type, and the ? is unrelated to being required. You can have non-nullable optional parameters and nullable required parameters, so it's not a given that the ! should go there, but it obviously can.

(For the last parameter, it would still go after the ), so:

void foo({int! bar, Map<String, String>?! baz, int qux(int value)!}) => ...

That's where the type ends and the ? goes for nullable function parameters.

@manyopensource
Copy link
Author

No, the ! goes where the ? should go:

void foo({int! bar, Map<String, String>?! baz, int! qux(int value)}) => ...
//or
void foo({int! bar, Map<String, String>! baz, int! qux(int value)}) => ...

@lrhn
Copy link
Member

lrhn commented Jul 21, 2020

The ? does not go on the int of int qux(int value) when the parameter is nullable.
An int? qux(Int value) means that the return type of the function parameter is nullable, not that the parameter itself is, so the ! would also have to be placed after the (int value). So:

void foo({int! bar, Map<String, String>?! baz, int qux(int value)!}) => ...

is placing the ! where the ? would go for the parameter type.

@manyopensource
Copy link
Author

manyopensource commented Jul 21, 2020

I'm not sure that the int qux(int value) is even legit as a named parameter! What is this - are you defining a function there? If so okay!

I thought it's done like that:

void foo({int bar, Map<String, String> baz, Function qux}) => ...

@lrhn
Copy link
Member

lrhn commented Jul 21, 2020

Dart has two ways to specify function-typed parameters: a prefix Function-based type and an infix C-like notation:

void f({int Function(int) callback});
void g({int callback(int value)});

Both work, and when you need make then nullable for null-safety, you do it as:

void f({int Function(int)? callback});
void g({int callback(int value)?});

where the ? goes after the "end of the type", which is after the end of the parameters.

@micimize
Copy link

I propose this issue be updated to ! Shorthand for required named parameters, i.e. f({this.foo!, int! bar}) and fold in similar discussion from #878 (! proposal comment, my summary comment).

I think ! is the most viable approach to mitigate the required verbosity pain-point. It seems to me the most intuitive, however semantically it is a bit hairy in dart. My takes:

👍 ! can already be viewed as the inverse of ? elsewhere (foo?.bar is "it's ok that it's null vs foo!.bar is "I know it isn't null").

👎 Null is certainly proximal to required conceptually, but isn't the same, potentially making int, int!, int?, int?! a confusing set of syntax elements to new dart devs.

👍 Devs already know how to handle different symbol meaning in different contexts (int? foo vs bar ? foo : baz vs foo?.bar).

👎 However, IMO this would collide conceptually with is! due to the ! as bool operator suffix.

👎👎 Having multiple valid syntax elements with the same meaning creates noise and potential frivolous style arguments that dart has carefully avoided until now.

The last point is IMO a big issue for this proposal unless the entire community wants int! foo over required int foo. However, unless some other language affordance lessens this pain point (i.e. some facet of #831) it is hard to think of a good alternative.

One alternative approach could be to tackle this pain point at the editor level: configurably auto-fill required instead of complaining in the language server, and have an editor extension that rewrites/hides required. The later could be a bit janky, and both would be major directional decisions of how the dart teams want their tooling to work.

@Levi-Lesches
Copy link

@tatumizer asterisks are also popularly used to designate pointers, so it'd probably be best to avoid that confusion.

@manyopensource I don't really see the benefit of using an already widely-used punctuation mark (that many people want to extend to have even more meanings) to replace a word that says what you mean. Pretty much every IDE can save you a few characters by offering required after you type re (which is arguably easier to type than SHIFT + 1), and in terms of readability, well, nothing really beats a word that clearly spells out its intention. A few more detailed points:

As @munificent pointed out, while some view ! as meaning "not nullable", many view it more literally as a way to get the compiler to stop complaining. That's certainly a stance taken by the Dart team before, with the logic that seeing ! in random places in your code should make you stop and think about what you're doing. Obviously that's not its only use, you have !bool, but if anything, that just goes to show that identifier! and !identifier are both already taken for very different meanings.

Devs already know how to handle different symbol meaning in different contexts (int? foo vs bar ? foo : baz vs foo?.bar).

You're kinda just saying that we're already used to punctuation instead of words, but it's different here. For both a ? b : c and a?.b, the punctuation represents control flow: "if a, then choose b. Otherwise, choose c", and "if a is not null, then get a.b. Otherwise, evaluate to null". Other punctuation, like (), {}, and $, also represent control. As for int?, that's taking the question mark more literally: "this is an int... maybe?". In the current use of maybeNull!, the ! is control flow: "evaluate to maybeNull, unless it's null, in which case throw". Using it for required parameters doesn't really fit those patterns.

One alternative approach could be to tackle this pain point at the editor level: configurably auto-fill required instead of complaining in the language server, and have an editor extension that rewrites/hides required

That is a lot of effort an confusion just to avoid a keyword which, again, clearly states its purpose. There's no reason to omit it since it's not confusing and not ambiguous. Sure it's a few more characters but using ! is confusing and omitting the keyword altogether is misleading, which IMO are way worse.

Also, see https://xkcd.com/1306/, especially the alt-text: "The cycle seems to be 'we need these symbols to clarify what types of things we're referring to!' followed by 'wait, it turns out words already do that.'"

@micimize
Copy link

@Levi-Lesches I kinda see your punctuation-as-control, but I meant that point more as a response to "! is already used elsewhere." There is certainly a weaker analogy for int! compared to int?, but I would say it's in the same genre of type information – just type info of the function rather than the argument.

I made an SDK issue for my auto-fill proposal: dart-lang/sdk#48119. I think you're mostly responding to the rewrite extension – I get why that'd be more controversial. However, having the option to let the machine fill in required could increase dev productivity and increase usage of named params.

just to avoid a keyword which, again, clearly states its purpose

Every required statement outside of a typedef provides no information (a non-null param with no default must be required to be valid), and it can be a lot of noise. I get the arguments for maintaining consistency in the core spec, but pragmatically, brevity has value. That's why it isn't nullable int foo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

5 participants