language
language copied to clipboard
Allow both optional positional and optional named arguments in the same function signature.
Dart functions currently can have only either optional named parameters or optional positional parameters, but not both.
While developing libraries repeatedly run into cases where this restriction gets in the way of the signature we want. It would be preferable if you could have both kinds of parameters on the same function. The only syntax change necessary is to the declaration of functions. The call syntax is unaffected, and so is noSuchMethod/Function.apply.
This issue is related to issue dart-lang/sdk#6496, but asks for less.
This comment was originally written by @seaneagan
Is this asking for:
- 2 separate optional parameters within the same declaration, one named, one positional
- allowing a single optional parameter to be both named and positional
The former could be solved by non-overlapping [] and {}. If overlapping [] and {} are allowed, then would have to decide whether to allow both {[ and [{ as well as }] and ]].
It is asking for non-overlapping [] and {}. Examples: new List([length = 0], {fill: null}); Stream.subscribe([onData], {onError, onDone}) new Date(year, [month, ...., milliseconds], {isUtc: false})
Added this to the Later milestone. Added Accepted label.
This comment was originally written by @seaneagan
Sounds perfect! The only other thing I would change (also mentioned in issue dart-lang/sdk#6496) is to use = instead of : for named positional default values:
new List([length = 0], {fill = null});
The = to default positional optionals doesn't mimic call site syntax, so why does the named positional default syntax try to? I think it's more importatnt to be consistent between how to default optional parameters regardless of whether they are named or positional.
This comment was originally written by @rbishop-bah
Related: Issue dart-lang/sdk#17101
Removed this from the Later milestone. Added Oldschool-Milestone-Later label.
Removed Oldschool-Milestone-Later label.
Issue dart-lang/sdk#17101 has been merged into this issue.
Marked this as blocking dart-lang/sdk#21406.
Since 2.0 is thinking of some drastic changes is there any way the more extreme do like C#, Python, of dart-lang/sdk#6496 can be revisited.
@gbracha would a DEP be required for this?
@donny-dont if this issue is fixed then we will be able to do:
greet([String salutation = 'Hello'], {String who}) {
print('$salutation $who!');
}
Then if there is really a need to allow parameters to be both named and positional, we could further allow something like this syntax:
greet({[String salutation = 'Hello', String who]}) {
print('$salutation $who!');
}
But personally, I think that creates unnecessarily bloated API surface.
Any guesses when this might get implemented?
I would like to have the ability to have a parameter be either positional or named, for a common usage case in Flutter -- the children/child of a widget (note that this case also applies to any place where you are building tree-structured values).
Consider the following Flutter widget tree:
new Center(child:
new Column(children: [
new Text("Hello, World!"),
new Icon(Icons.star, color: Colors.green)
])
)
Once "new" is optional, this starts looking like a reasonable replacement for HTML, as soon as we can treat child or children arguments as positional, like this:
Center(
Column([
Text("Hello, World!"),
Icon(Icons.star, color: Colors.green)
])
)
We kinda already have this in that Text doesn't specify the 'Hello, World!' string as a child, even though it kinda is. Same thing for Icon.
It would be nice (albeit not required) if the positional argument did not have to be the first argument so you could specify the named arguments first before following them with the children specified as a positional argument.
I would love to see this, together with making child en children positional in Flutter 🙏
In maintaining the protobuf library we have functions that take positional optional arguments, making it hard to add further optional arguments. Refactoring the existing functions to take only named arguments is probably the 'right thing to do(tm)' but that is a breaking change and thus very costly.
For example GeneratedMessage.fromBuffer
currently has the signature
void mergeFromBuffer(List<int> input,
[ExtensionRegistry extensionRegistry = ExtensionRegistry.EMPTY]);
If we want to add more options to the parsing (for example bool ignoreUnknownFields
) it would be nice to add as a named optional, but that is currently not possible.
Something new? This sounds like a useful feature...
Is such a thing allowed now?
Future<Database> connect({String dbName, [String dbUserName, String dbPassword]}) {}
connect(dbName:"sample"); connect(dbName:"sample", dbUserName:"username", dbPassword:"password");
That wasn't the problem. The problem was using them as separate, and not nested, like this:
Future<Database> connect({String dbName}, [String dbUserName, String dbPassword])
I too ran today into this while working on a new API and I actually don't understand this restriction. If it's possible to mix named and positional parameter , why can't that positional not being optional?
I agree. Why we cant have both: positional and named optional parameters at the same time?
There were a handful of comments that were all either just "+1" or "+1 for this feature", which is better expressed by adding a 👍 to the issue itself, so I deleted them.
There are lots of confusing and conflicting statements in the comments on this issue. What I was just looking for--and what I feel @lrhn was asking for when opening this issue nine years ago--was the ability to have both an optional positional argument and an optional named argument (see function4, below). While looking into this, it appears to me that Dart actually doesn't allow any arguments of any type after the closing bracket of an optional positional argument (see functions 4, 5, and 7)!
void function1 (int reqdPosn, {required int? reqdNamed}) {} // allowed
void function2 (int reqdPosn, {int optNamed=1}) {} // allowed
void function3 (int reqdPosn, [int optPosn=1]) {} // allowed
void function4 ([int optPosn=1], {int optNamed=1}) {} // error
void function5 ([int optPosn=1], {required int reqdNamed=1}) {} // error
void function6 ({int optNamed=1}, [int optPosn=1]) {} // error*
void function7 ([int optPosn1=1], [int optPosn2=1]) {} // error
void function8 ([int optPosn1=1, int optPosn2=1]) {} // allowed
// * Yes, this violates the 'All positional arguments must be before any named
// arguments" rule but I though it worth testing for the sake of completeness!
I see no inherent confusion (to parsers or humans) in allowing both types of arguments but if there is, perhaps it would be best to state it (them?) and close this issue.
IMO, I suspect that the requested feature is wanted the most when one is adding functionality to an existing function that has an optional positional argument. My own takeaway (for the microscopic amount it is worth) is that I will rarely use optional positional arguments in the future--the little bit of typing this saves is just not worth it when compared to the effort that I now know will be required to add any named arguments in the future.
My own takeaway (for the microscopic amount it is worth) is that I will rarely use optional positional arguments in the future--the little bit of typing this saves is just not worth it when compared to the effort that I now know will be required to add any named arguments in the future.
You are not the only one thinking so, and some teams have a "no optional positional parameters ever" policy. That's not necessarily the best API design, but it's driven by the available features and a wish to avoid future regret.
That's why we should still add this feature. Every year we delay is another year of people doing sub-optimal API design, for APIs which might stay with us forever.
(One alternative is to change the parameter semantics completely, so that any nullable parameter is optional - aka. can be omitted, which then passes null
implicitly, and passing null
triggers the default value if there is one. E.g. #836. Since both positional and named parameters can be nullable, both can be optional at the same time).
@Irhn what about python like args/kwargs: def fun(*args, **kwargs): pass
abstract class Controller {
String render(String template, [List<Map<String, Object?>> *contexts /*= const []*/, ]{Map<String, Object?> **data /*= const {}*/}) {
data = contexts.reduce(...) + data;
return environment.getTemplate(template).render(data);
}
}
abstract class Template {
String render(Map<String, Object?> context);
}
abstract class Environment {
Template getTemplate(String name);
}
@irhn what about python like args/kwargs:
def fun(*args, **kwargs): pass
Python's approach doesn't work well in a statically-typed language like Dart where you want to be able definitely associate different static types with each optional parameter.
Every year we delay is another year of people doing sub-optimal API design, for APIs which might stay with us forever.
This is really is the most important argument for prioritizing this feature, IMHO. I'm currently designing a new API, and there are simply method signatures that beg to have optional, positional arguments – but I can't use them, because I need named ones, too.
So once the API is published, I'm going to be stuck with the suboptimal signature. Which will be a small stab in my heart every time I'm going to call it. 😐
So seeing that this was first proposed nine years ago, and nobody really posted a show-stopper as for the logic or sense of the feature – I think there's a chance for you guys to make a lot of people really happy by finally making a decision. 😜
Since dart-lang/sdk#47451 has been released, I believe this could be revisited, maybe after all objectives at dart-lang/sdk#47451 have been completed, but maybe it's mature enough. @lrhn
I, sadly, don't think "named arguments anywhere" has any effect on the viability of this issue. Named arguments anywhere is a simple reshuffling of arguments at the call site, it does not affect the declaration of functions. Allowing both named and optional positional arguments on the same function would affect function declarations and the low-level function call protocol.
Any knowledge on how someone can assist on that? I just have no idea on if this could be developed from outsiders and where to look for potential solutions/problems.
We definitely love getting help from external contributors. :)
But this is a particularly tricky issue because it affects the language's calling convention. That requires work in all of our implementations: VM, native compiler, development web compiler, and optimized web compiler. Any of those could potentially have negative performance implications that might make the feature untenable.
So to make progress on this, we really have to talk to all of the implementation teams and see how they think they would support it and what the performance costs might be before we could make any decisions.