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

Problem: Syntax for optional parameters and required named parameters is verbose and unfamiliar #15

Open
kasperpeulen opened this issue Aug 16, 2018 · 50 comments
Labels
request Requests to resolve a particular developer problem

Comments

@kasperpeulen
Copy link

The current syntax for optional positional parameters is:

foo([bool optional = false])

The current syntax for required named parameters is:

foo({@required bool optional = false})

Both syntaxes are not used in any other mainstream language that I know of and are unnecessary verbose compared to possible alternatives.

@Hixie
Copy link

Hixie commented Aug 16, 2018

Have we seen any problems with people facing this syntax? I don't recall anyone even blinking at this syntax in the Flutter usability studies, but it's been a while. cc @InMatrix

@kasperpeulen
Copy link
Author

@Hixie I think it obvious that if you come from any other language, and have no experience with Dart, that you are not going to guess this syntax yourself. Or find it by trial and error. You have to learn this syntax by reading documentation or googling it.

Personally, I have done a lot of development with Dart in the past. Not done much the last 2 years. And I'm now actively learning Flutter (and relearning the latest changes to Dart). I have googled "how the do the required named parameter in Dart" at least 2 times when learning Flutter. Before remembering, "Oh wait I needed to import a package for that."

@pulyaevskiy
Copy link

I see current behavior of named parameters as a really nice feature actually, they scale really well when API evolves.

Also proposal in #16 turns the problem 180 degrees and enforces verbosity for "new optional named arguments" by requiring to always specify default value.

Re: optional args - even though square brackets are not often used in those languages it's how they are documented in many of the above mentioned languages (maybe even most), ex.: php, nodejs. So current syntax is familiar in this regard. I'd be surprised if a developer coming from a language like JS or PHP would be confused by this syntax.

For me it seems like Dart is just following the idea of code being the best documentation. Personally I like current syntax as it's a lot easier to parse and it does look familiar to me as a developer coming from other language(s).

@matanlurey
Copy link
Contributor

To note, though I'll add more on #16, @required is not a feature of the Dart language, it is an optional analysis hint provided by the analyzer that omitting this parameter is not what the API author intended. For your own projects, you could omit it entirely if you don't like seeing it (but of course, you'll miss the static analysis hint).

@InMatrix
Copy link

@Hixie We had study participants (new Dart users) call methods with optional parameters and named parameters, and no obvious issues were observed. However, we have few observations of new Dart users constructing methods with those types of parameters.

@leafpetersen
Copy link
Member

To note, though I'll add more on #16, @required is not a feature of the Dart language, it is an optional analysis hint

This is the bit that bothers me most here - if one of our major platforms is going to the trouble to use an annotation to get this minor feature, it's a pretty strong signal that this is something missing. I'd like to see us incorporate this into the language.

@matanlurey
Copy link
Contributor

Agreed @leafpetersen.

And that's not even counting the issues with forwarding today with named arguments with default parameters; if I remember correctly @munificent is quite aware of the issues here:

void method1({a = 1}) => print(a);
void method2({a}) => method1(a: a:);

void main() {
  method1(); // "1"
  method2(); // "null"
}

@Hixie
Copy link

Hixie commented Aug 17, 2018

In practice, package:meta is part of the language. Whether people put an @ character in front of the keyword or not really doesn't matter IMHO.

@lrhn
Copy link
Member

lrhn commented Aug 17, 2018

It's perfectly possible to write code without using package:meta. All that package can do is to allow extra warnings and errors that you opt-in to. Nothing in the package can allow a program that would otherwise be invalid, so the ability of the package to affect the language is limited.

That is, in part, why it's so successful. It can add restrictions that are too strict for everybody, something we would never do at the language level.

As for "required", I think it is a good idea, but that it is probably not really worth doing by itself.
If we add non-nullable types, or ptherwise improve the parameter syntax, then it's something that should be incorporated - and something that likely will be incorporated.
On the other hand, the perfect is the enemy of the good. We can add some syntax to make named arguments required now, and if we rework parameter syntax anyway, we can remove it again if it's no longer necessary.

With non-nullable types, we could just make any nullable typed parameter with no default, and any parameter with a default, optional. Then:

void foo(int x, int? y, int z = 42, {int u, int? v, int w = 37})

I'd like that.

@leafpetersen
Copy link
Member

With non-nullable types, we could just make any nullable typed parameter with no default, and any parameter with a default, optional. Then:

I don't think this works out well for positional parameters for all of the subtyping and call reasons we've been working through with Bob's UI as code proposal. For example:

void f(int? x, int? y) {print(x);};
f(3); // prints 3
void Function(int? x, int y) g = f;
g(3); // prints null

@munificent
Copy link
Member

Yeah, I don't think we should make nullable positional parameters implicitly optional because it affects all later parameters in ways the user likely doesn't intend. But for named parameters, where each parameter is independent of the others, I think it might work (though I haven't put a ton of thought into it).

I would like to support required named parameters but so far haven't come up with a syntax I like.

@Hixie
Copy link

Hixie commented Aug 21, 2018

I would like to support required named parameters but so far haven't come up with a syntax I like.

What's wrong with what we have today?

@lrhn
Copy link
Member

lrhn commented Aug 21, 2018

I would like to support required named parameters but so far haven't come up with a syntax I like.

What's wrong with what we have today?

The language doesn't have required named parameters, so if you are not using the analyzer, you won't even get a warning if you don't pass a value for a @required named parameter. So, basically, you have nothing today.
If you want to declare required named parameters, you also need to import package:meta/meta.dart.

If it were to become a language feature, then using an annotation is not good design. It's also problematic that even if we did that, we'd have to add a required declaration to dart:core, and then that would be hidden by any existing required declaration in package:meta.

@yjbanov
Copy link

yjbanov commented Aug 21, 2018

I think we should tackle this problem after we have nullability in the language, since the two seem to interact in ways that might require that we look into real world usage patterns. package:meta/meta.dart provides a good enough solution for now, particularly in Flutter apps, where the annotations are exported by the framework and therefore almost as good as being part of the language.

@Hixie
Copy link

Hixie commented Aug 21, 2018

The language doesn't have required named parameters, so if you are not using the analyzer, you won't even get a warning if you don't pass a value for a @required named parameter. So, basically, you have nothing today.

I don't think this matches our experience or the experience of our developers (that is, people using Flutter).

The analyzer is an integral part of the Flutter/Dart development experience, in the same way that debug mode asserts are an integral part of the development experience. You can certainly artificially limit your experience by not using the analyzer or not using debug mode, but I don't think it's worth optimising for people who do that because they're artificially making life harder on themselves. It's not clear to me there's anything to be gained by making apps crash if you omit the "onPressed" handler for a button, say (that's one of the arguments we have marked @required). They work fine today, the button is just disabled.

If you want to declare required named parameters, you also need to import package:meta/meta.dart.

Flutter re-exports package:meta so you don't actually have to when using Flutter.

If it were to become a language feature, then using an annotation is not good design.

From the perspective of our developers, the difference is a single @ character. I don't think many people in practice really give it a second thought. We use lots of annotations. @required, @optionalTypeArgs, @override, @visibleForTesting, @mustCallSuper, @protected, @Deprecated, @immutable... They're all part of the language that we're writing code in. Maybe you don't consider that Dart, but we (and our developers) do.

Based on our experience when we went from the @checked annotation to the covariant keyword, there really is no difference to the developer workflow whether the @ is present or not.

@leafpetersen
Copy link
Member

As a general principle, I don't like delegating core language features to annotations. If something is broadly used as an annotation, that's a strong signal that there's something missing from the language. Sometimes there's not a good way to incorporate something cleanly into the language, or sometimes the feature is too niche to adopt. But something like this seems very clear to me. It's widely requested, widely used via an annotation, it's something a compiler could use to generate better calling sequences, and it slots naturally into existing features (we have required and optional positional parameters in the language, why not required named?).

There's certainly a valid point in the language design space which is basically Lisp + annotations, but I don't think Dart should aim to be that language.

@matanlurey
Copy link
Contributor

The example of @checked -> covariant I don't think cleanly applies, since @checked was basically a (very old) "Dart 2 Preview" annotation that did have runtime impact (at least in DDC).

I am a little concerned about letting a framework (any framework, including mine) dictate the definition of the language by adding new annotations and then treating them as part of the language, but at the same time I do wish we had more static analysis checks than we have now and understand annotations are the only way to do them cleanly and non-breaking.

@Hixie
Copy link

Hixie commented Aug 21, 2018

I agree that any annotations added as analyzer extensions would not be considered part of the language. The ones I listed above are all supported natively by the analyzer, though, and I think it's fine to consider the analyzer to be part of Dart.

(We will hopefully one day also add our own annotations and an analyzer plugin, but we're not there yet. I would not consider those part of Dart in any meaningful sense, only part of Flutter.)

My original point was just that we already have required arguments, with a syntax that works, and which people demonstrably understand and use, so it would make sense IMHO to keep using it (or something very close to it, e.g. just removing the @) rather than trying to introduce new syntax using special punctuation or whatnot.

@munificent
Copy link
Member

I think we should tackle this problem after we have nullability in the language, since the two seem to interact in ways that might require that we look into real world usage patterns.

+1.

What's wrong with what we have today?

  1. Let's say the VM discovers they could optimize calls that pass required named parameters by, say, always allocating stack slots for them since they can never be omitted. That optimization isn't safe if this isn't a first-class language feature that generates a compile error.

  2. Or, imagine you are a package author. You change a named parameter to be required. Is this a breaking change? Do you need to bump the major version of your package? The answer depends entirely on whether you think @required is part of "the language" or not. Not having a consistent ecosystem-wide answer to that question can cause user programs to break when they upgrade what they thought were non-breaking changes.

We've gotten a lot of feedback over the years from many users that hints, lints, metadata annotations and other pseudo-language features cause a lot of confusion and fragmentation. One of the things users tell us they like most about Dart is that it's opinionated — there is usually a single canonical blessed way of doing something.

I think we should have an opinion about whether required named parameters are worth doing or not. If they are, lets do them right and give them good syntax full integrated into all of our tools and type system. (Personally, I think we should have them, but I think it might be best to integrate them with non-nullable types.)

it would make sense IMHO to keep using it (or something very close to it, e.g. just removing the @) rather than trying to introduce new syntax using special punctuation or whatnot.

+1. The last thing I want to do to parameter lists is add more punctuation. The [] and {} are bad enough. (Well, except maybe for rest parameters, but we would at least be adding familiar punctuation there.)

If I had to pick a syntax today, I would suggest just required.

@zoechi
Copy link

zoechi commented Sep 11, 2018

Does it make sense to have a required parameter that is nullable?
If not, using a non-nullable type should be enough, shouldn't it?

Also adding a default value would automatically require the parameter to be optional, so a default value could also be used to distinguish required from not-required fields. (update - just saw that's discussed in #16)

This way a new keyword wouldn't be required.

If something like allowing all parameters to be used like named or positional parameters (removing the distinction between positional and named) or rest parameters are considered, (and perhaps others I forgot) this should also be taken into consideration when changes to parameter syntax are discussed.

@Hixie
Copy link

Hixie commented Sep 11, 2018

FlatButton.onPressed is required and nullable.

@zoechi
Copy link

zoechi commented Sep 11, 2018

@Hixie right, I think I saw you mention this example already.
I assume this is for discoverability? Good point.

@Hixie
Copy link

Hixie commented Sep 11, 2018

We use "null" to indicate there's no behaviour and so the button should be disabled.
We require it because people got confused when the default was disabled, so we didn't want to have a default.

@denniskaselow
Copy link

denniskaselow commented Sep 18, 2018

As I have not yet seen this idea, and as one of the many things I like about Dart is that it isn't as verbose as some other languages and still easily readable, how about:

void f(int a, int b, required {int c, int d});

It's less verbose than adding the keyword to every parameter (or setting every optional parameter to null like in #16) you want to make required and named and it doesn't change the meaning of {...} as a parameter. By default these are still named parameters that are optional, and if you want to make some required you can do so. It'll also keep the function signature clean because you are not able to mix required and optional parameters like.

void f(int a, int b, {required int c, int e, required int d});

and will have to do this instead:

void f(int a, int b, required {int c, int d}, {int e});

Furthermore (in my opinion), this declaration would also clearly state that those parameters are required when overriding a method. No idea whether anyone would want to have it any other way.

Possible other syntax instead of required {...}: &{...} (maybe not that good for people coming from pointy languages) or {...}!.

@munificent
Copy link
Member

Putting required before the { is pretty clever. Thanks for bringing that up!

@MichaelRFairhurst
Copy link

Having {...} default to optional would be good for backwards compatibilty, but does anyone have a good idea of whether optional or required is more likely?

I'd imagine that if required is the intention for >30% of cases, it'd be a better default.

Then we could hopefully allow explicit optional:

f(int a, required {int b, int c}, optional {int d, int e});

and make a lint for implicit optional, such that we could switch the default for maybe dart 4.

@munificent
Copy link
Member

does anyone have a good idea of whether optional or required is more likely?

When I last looked at the Flutter repo, the wide majority of named arguments were not required.

Other repos aren't a good signal since most users don't know @required is available and so may be designing their APIs assuming all named args have to be optional.

I do generally think optional will be the norm for named args even if we put required named arguments into the language. A big part of why you want to be able to pass by name (which is more verbose) instead of position is to be able to pick and choose which you pass. If they're mandatory, it starts to make more sense to make them positional.

@yjbanov
Copy link

yjbanov commented Sep 19, 2018

A big part of why you want to be able to pass by name (which is more verbose) instead of position is to be able to pick and choose which you pass. If they're mandatory, it starts to make more sense to make them positional.

I think it much more than that. Named arguments provide certain flexibility that positional arguments do not. For example, a named argument that is required today may become optional in the future without breaking the API.

Many (if not most) of Flutter named arguments map onto class properties with the same names, and many of them share types (e.g. TextStyle). Argument names make the code much more readable by telling you which property is being set without you having to deduce from type, which is inexact, or memorize which property corresponds to which position in the argument list.

I kinda like @denniskaselow's suggestion. Another variant might be to allow making positional arguments nameable via a named keyword:

void f(int a, int b, named int c, named int d, {int e});

But @denniskaselow's suggestion composes better with the existing notion that everything inside { } is named.

@mit-mit mit-mit added the request Requests to resolve a particular developer problem label Nov 8, 2018
@red010b37
Copy link

red010b37 commented Jul 2, 2019

I quite like the annotation the biggest issue IMO, is the fact that the compiler does not break if the param is missing - hence no feedback to the developer.

Positional param
foo(bool bar)
Calling foo(); - breaks the compile - dev knows they have messed up

Annotated Params
foo({@required bool bar})
calling foo() - everything is 💯 - continue on nothing to see here - but it is required, this can cause bugs to be deep and hard to find. The compiler should break and not let the program run.

at the moment for my apis to enforce the required state
I do void foo(int x, y, {int: a, int: b})

@eernstg
Copy link
Member

eernstg commented Jul 3, 2019

Note that required named parameters will be introduced as a language mechanism with NNBD (cf. this section for the syntax). This means that it will be a compile-time error (so there's no need to enable a lint, and it cannot be ignored) to omit such a parameter in an invocation, and the function type subtype rules will ensure that this property is not "forgotten" during an up- or downcast.

@kasperpeulen
Copy link
Author

kasperpeulen commented Jul 4, 2019

@eernstg But wait, what does it mean, if you have a non nullable parameter without a default value, which is not annotated to be required?

So foo({int bar}) for an example. If I now call foo(), do I get a compile time error, as bar is non nullable? I guess it must be. Is it a different error then when I write foo({required int bar})?

@eernstg
Copy link
Member

eernstg commented Jul 4, 2019

non nullable parameter without a default value, which is not annotated to be required?

That's a compile-time error in the declaration of the parameter, so there's no need to worry about how it would work for invocations.

A declaration like foo({required int bar}) is ok, and then it's a compile-time error to invoke it like foo().

@kasperpeulen
Copy link
Author

@eernstg Hmm.. what about inferring it to be a required parameter? You could safely do that.

@eernstg
Copy link
Member

eernstg commented Jul 4, 2019

Indeed, and we discussed that option, too. It is a trade-off between making the code concise and well-documented (for instance, a type variable T can be potentially nullable, so you can't see whether a given parameter is required just by checking that there's no ? on the type).

@rrousselGit
Copy link

rrousselGit commented Jul 4, 2019

Can't we have both?

void foo({int a});
foo() // compile error, a is required

void bar({int? a});
bar() // valid

void baz({required int? a});
baz() // compile error, a is required
baz(a: null) //valid

void qux({int a = 42});
qux() // valid
qux(a: null) // compile error

And then for generics:

void foo<T>({T a});

foo<int>() // compile error
foo<int>(a: 42) // valid
foo<int?>() // valid

void bar<T>({T? a});
bar<int>() // valid

void baz<T>({required T? a});
baz<int>() // compile error
baz<int?>() // compile error

@kasperpeulen
Copy link
Author

@rrousselGit I would really like this. In practice this would mean that I can skip the required annotation allmost everywhere I use it.

@eernstg
Copy link
Member

eernstg commented Jul 5, 2019

That's an interesting idea for the generic case. It basically means that the parameter a of foo<T> is required iff T is potentially non-nullable.

But T being potentially non-nullable just means that we can't prove Null <: T, which is true whenever T is a type variable with an upper bound: If we only know T extends int then T is non-nullable because an int can't be null, but even if T extends int?, T extends Object?, or T extends dynamic, we could still have T == int, so T would still be potentially non-nullable. So any context where such a type variable is only known by an upper bound would make the parameter required.

One example is an instance method:

abstract class C<X> {
  void foo({X x});
}

main() {
  C<int?> c = Random().nextBool() ? C<int>() : C<int?>();
  c.foo(); // Compile-time error, could be a `C<int>`.
}

Another difficulty is that the ability to recognize that the named parameter is sometimes-optional disappears when we assign a function to a function-typed variable or parameter:

void foo<X>({X x}) {}
void Function<X>({X x}) f1 = foo; // Error!

// This is OK, but forces `x` to be always-required, no matter which
// `X` we have at a call site.
void Function<X>({required X x}) f2 = foo;

Function types in Dart do not support declarations of default values, so whenever we pass a function as a first class value we need to represent the requiredness of any given named parameter explicitly (hence {required X x} with f2 above).

So the initialization of f1 to foo is an error because that function type implies that x can always be omitted (which could be because the actual value has type void Function<X>({X? x}), which is a subtype of void Function<X>({X x}) and hence ok for f1, or because the actual value of f1 has a default value for x).

On the other hand, the initialization of f2 is allowed, but this means that we must always pass x for invocations of f2.

So we do get a little bit of extra flexibility, but that flexibility tends to evaporate as soon as we combine this idea with other language mechanisms.

@leafpetersen
Copy link
Member

Can't we have both?

void foo({int a});
foo() // compile error, a is required

We discussed this option, but decided against it because it makes things confusing when you interact with function types, for which you would need to write required, since there are no default values to infer from. So foo({int x}) as a function definition means that x is required, but foo({int x}) as a function type means that x is required. Cut and paste the signature of a function from its definition and it changes meaning. Confusing.

typedef int FooType({int x}); // x is optional here
int foo({int x)} => x; // x is required here
void takesFoo(int foo({int x})) {} //  x is optional here
takesFoo(foo); // Error, types don't match
FooType f = foo; // Error, types don't match

@rrousselGit
Copy link

it because it makes things confusing when you interact with function types, for which you would need to write required, since there are no default values to infer from

To be fair, it's default values that are confusing, not that suggestion:

void foo({ int x = 42}) => print(x);

foo(); // 42
foo(x: null); // null

There's a difference in behavior between both calls, but the type definition doesn't suggest that there's any difference at all.

With non-nullable types, I think we could include that behavior inside the type definition itself:

typedef NullableOptional = void Function({ int? x });
typedef NonNullableOptional = void Function({ int x = });

Which translates into:

NullableOptional a;
a(); // valid
a(x: null); // valid

NonNullableOptional b;
b(); // valid
b(x: null); // compile error

@rrousselGit
Copy link

abstract class C<X> {
 void foo({X x});
}

main() {
 C<int?> c = Random().nextBool() ? C<int>() : C<int?>();
 c.foo(); // Compile-time error, could be a `C<int>`.
}

That's a fair point. In that case, an alternative is to disable the inference for generics such that:

void foo({ int a });

compiles by being inferred to:

void foo({ required int a });

but:

void baz<T>({ T a});

doesn't compile at all.

@eernstg
Copy link
Member

eernstg commented Jul 9, 2019

an alternative is ..

Agreed, we could do some or all of those things. At the same time, I think this illustrates that this would introduce additional complexity for any reader of Dart code, mainly because we gave such a high priority for writers to have the option to omit the word required.

@rrousselGit
Copy link

Right.
At the same time, I do think this is important. After all, the convention for widgets parameters in Flutter is for them to all be named.
Similarly Clean Code, too, suggest to use required named parameters in some situations, just for the sake of readability.

So having to write this required keyword could add a non negligible amount of boilerplate to a very common behavior, while also breaking thousands of articles/answers on Stackoverflow/videos.

I'm not too sure how to have real stats backing that statement though.

@eernstg
Copy link
Member

eernstg commented Jul 9, 2019

There is one device which could help with the verbosity while keeping requiredness explicit:

void foo(required {T1 n1, T2 n2}) {...} // All named parameters are required.

@leafpetersen
Copy link
Member

So having to write this required keyword could add a non negligible amount of boilerplate to a very common behavior, while also breaking thousands of articles/answers on Stackoverflow/videos.

I'm not too sure how to have real stats backing that statement though.

I believe that using @required is standard in the framework code, so that gives one place to look for data - you can hack up something to rewrite it using different styles. I experimented with one possible syntax this way.

@munificent
Copy link
Member

There is one device which could help with the verbosity while keeping requiredness explicit:

void foo(required {T1 n1, T2 n2}) {...} // All named parameters are required.

I'm not sure where I wrote up the results, but I did some digging into use of @required in the Flutter framework. One finding was that a very large number of required named parameters were not contiguous. It was common to see an interleaved mixture of optional and required named parameters. Further, the Flutter framework strongly cares about that parameter order because it affects things like auto-complete in IDEs.

From that, I think it implies we don't want a syntax that forces all required named parameters to be separated from the optional ones.

@eernstg
Copy link
Member

eernstg commented Jul 10, 2019

we don't want a syntax that forces all required named parameters
to be separated from the optional ones

There's no need to force anything, required {...} would just be a more concise syntax for the cases where all named parameters are required; or required {...} could reverse the default and we could have optional on non-required named parameters in {...}. Or we could allow multiple {...}s in parameter declaration lists (maybe insisting that they're kept together at the end such that an expansion to the form where every required is on a single parameter is a trivial piece of syntactic sugar). So there are many things we could do in order to ease the burden of writing all those required keywords on parameters, and still keep requiredness explicit in function signatures.

@kasperpeulen
Copy link
Author

That's an interesting idea for the generic case. It basically means that the parameter a of foo is required iff T is potentially non-nullable.

Yes, in other words, a named parameter with no default value is required iff it is not possible to give it implicitly the default value of null.

About your example:

abstract class C<X> {
  void foo({X x});
}

main() {
  C<int?> c = Random().nextBool() ? C<int>() : C<int?>();
  c.foo(); // Compile-time error, could be a `C<int>`.
}

What would happen in this example with the current NNBD/required proposal?
I think this example is a problem in either case.

According to the type definition the parameter x is optional, but if I would do:

var c = C<int>();
c.foo();
// error, int can not be null

It would follow that in this instance the parameter x is not optional
In other words, we can not safely guarantee that the parameter x is truly optional.

So I would already expect either a compile time error in the declaration of the parameter that x should be required, or as @rrousselGit proposes, infer x to be required:

abstract class C<X> {
  void foo({X x});
}

If you want the parameter x to be optional, you should write:

abstract class C<X> {
  void foo({X? x});
}

This is also how it works in Kotlin, if you want a parameter of type generic X to be optional, you should make it nullable:X? and assign it a default value of null (Dart does this implicitly).

@lrhn
Copy link
Member

lrhn commented Jul 10, 2019

@kasperpeulen
The issue here is still that you cannot see from the type signature whether a function parameter is required.

Example:

class Foo {
  int foo({int x = 42}) => x;
}
int Function({int x}) f = Foo().foo;
f();  // Allowed? If not, which type *should* f have to make it allowed?

It means that the type system for functions needs a way to recognized optional parameters, even if they are non-nullable.
We could make the presence of a default value part of the function signature, but it's easier to just mark every required parameter in the function type with required (or any other marker).
That also allows a required nullable parameter (whether that's a good idea or not, my vote is that it's bad, m'kay).

@rrousselGit
Copy link

rrousselGit commented Jul 10, 2019

class Foo {
 int foo({int x = 42}) => x;
}
int Function({int x}) f = Foo().foo;
f();  

IMO, in a context where required is infered, that shouldn't compile here:

int Function({int x}) f = Foo().foo;

As I said here #15 (comment), I think it's important to include default values as part of the function definition.

Doing so allows optional non-nullable optional parameters:

int foo({int x = 42}) => x;
foo() // valid
foo(x: null) // compile error

@eernstg
Copy link
Member

eernstg commented Jul 10, 2019

@kasperpeulen wrote:

What would happen in this example with the current NNBD/required proposal?

void foo({X x}); would be an error because X is a potentially non-nullable type and x is optional and has no default value. So we don't even go into a situation where the parameter is required or not, based on the value of X, we make sure that requiredness can be determined up front.

we can not safely guarantee that the parameter x is truly optional.

Exactly, which is the reason why that whole concept (of making x required iff the type makes that necessary) is difficult to handle.

If you want the parameter x to be optional, you should write: ... {X? x}

Right, we could rely strictly on the type, but this means that it would be impossible to specify that a parameter has a non-nullable type and may still be omitted (because there is a default value). And, who knows, it might even be inconvenient that you also can't specify that a parameter has a nullable type and is required.

For instance, the only way we can allow a function f of type void Function(int i, {String x}) to be assigned to a variable/parameter v of type void Function(int i) is when x is optional, so it may be inconvenient if we can't express that concept separately (note that void Function(int i, {String? x}) is not a supertype, so we may have to create a wrapper function g and assign that to v).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
request Requests to resolve a particular developer problem
Projects
None yet
Development

No branches or pull requests