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 both optional positional and optional named arguments in the same function signature. #1076

Open
lrhn opened this issue Nov 30, 2012 · 51 comments
Labels
request Requests to resolve a particular developer problem

Comments

@lrhn
Copy link
Member

lrhn commented Nov 30, 2012

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.

@floitschG
Copy link

This issue is related to issue dart-lang/sdk#6496, but asks for less.

@DartBot
Copy link

DartBot commented Nov 30, 2012

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 ]].

@floitschG
Copy link

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})

@gbracha
Copy link

gbracha commented Nov 30, 2012

Added this to the Later milestone.
Added Accepted label.

@DartBot
Copy link

DartBot commented Dec 1, 2012

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.

@DartBot
Copy link

DartBot commented Feb 25, 2014

This comment was originally written by @rbishop-bah


Related: Issue dart-lang/sdk#17101

@kasperl
Copy link

kasperl commented Jul 10, 2014

Removed this from the Later milestone.
Added Oldschool-Milestone-Later label.

@kasperl
Copy link

kasperl commented Aug 4, 2014

Removed Oldschool-Milestone-Later label.

@gbracha
Copy link

gbracha commented Aug 27, 2014

Issue dart-lang/sdk#17101 has been merged into this issue.

@lrhn
Copy link
Member Author

lrhn commented Oct 28, 2014

Marked this as blocking dart-lang/sdk#21406.

@donny-dont
Copy link

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?

@seaneagan
Copy link

@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.

@bobjackman
Copy link

Any guesses when this might get implemented?

@wmleler
Copy link

wmleler commented Jan 2, 2018

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.

@kasperpeulen
Copy link

I would love to see this, together with making child en children positional in Flutter 🙏

@sigurdm
Copy link
Contributor

sigurdm commented Aug 13, 2019

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.

@lrhn lrhn transferred this issue from dart-lang/sdk Jul 8, 2020
@lrhn lrhn added the request Requests to resolve a particular developer problem label Jul 14, 2020
@eladcandroid
Copy link

Something new? This sounds like a useful feature...

@blunderous
Copy link

blunderous commented Aug 26, 2020

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]) 

@escamoteur
Copy link

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?

@vovkd
Copy link

vovkd commented Jan 28, 2021

I agree. Why we cant have both: positional and named optional parameters at the same time?

@dart-lang dart-lang deleted a comment from ericloureiro Apr 7, 2021
@dart-lang dart-lang deleted a comment from maurodibert Apr 7, 2021
@dart-lang dart-lang deleted a comment from lluchkaa Apr 7, 2021
@dart-lang dart-lang deleted a comment from aktxyz Apr 7, 2021
@rrousselGit
Copy link

@lrhn There seems to be an infinite recursion in that issue link list :P

What's the issue with tracking this?

@eernstg
Copy link
Member

eernstg commented Feb 13, 2023

@lrhn, did you intend to close some other issue which is subsumed by this one?

@dcharkes dcharkes reopened this Feb 13, 2023
@lrhn
Copy link
Member Author

lrhn commented Feb 13, 2023

Whoops, yes. I thought I was looking at an SDK-repo issue. I followed a link with dart-lang/sdk/ in it to get there.
I just hadn't noticed the issue had been moved here, and the old link was forwarding, so it's all the same issue.

@JaffaKetchup
Copy link

Is there any chance of this coming in Dart 3? It would seem like an ideal time for it, considering that this has been opened for 11 years!

@mateusfccp
Copy link
Contributor

Is there any chance of this coming in Dart 3? It would seem like an ideal time for it, considering that this has been opened for 11 years!

I'm pretty sure it won't...

@munificent
Copy link
Member

Sorry, no, this won't make it into Dart 3. I would like to fix it at some point, but it's one of those issues that's annoying but not troublesome enough to reach the top of the priority list.

@JaffaKetchup
Copy link

JaffaKetchup commented Apr 4, 2023

Sad to hear that, but thanks for letting us know. Just looking forward to Dart 3 now :)

@escamoteur
Copy link

I'm currently overhauling the APIs of get_it and get_it_mixin. It really would make APIs much better if we at least had the first parameter an optional positional that can have a default value and get mixed with named ones that follow

@eernstg
Copy link
Member

eernstg commented May 31, 2023

@escamoteur, it would be interesting to see the concrete example, can you show it using a small snippet of code?

One of the sources of complexity in this feature is the need to support a different calling convention (in particular, such that invocations of first class functions using the type dynamic or Function can work, or even statically checked invocations on a supertype). This would potentially require a substantial amount of work in several different backends.

If we're satisfied with a static mechanism then we do have a lightweight proposal here: #831, 'optionally named parameters'.

The idea is that some parameters can be named, but they are marked as 'optionally named', and they can then be passed as positional parameters. At each call site, a compile-time transformation will check the actual argument list, cut off any 'extra' positional parameters (so if the function declares 1 positional parameter, but we're passing 3, we have 2 extras), and re-adding those extra parameters with a name (so foo(1, 2, 3, z: 4) becomes foo(1, x: 2, y: 3, z: 4)). The declaration specifies which named parameters are subject to this transformation, and in which order.

The main limitation is that (foo as dynamic)(1, 2, 3, z: 4) will fail because it will be called exactly as written (the compile-time transformation won't change any dynamic calls), and foo doesn't actually accept 3 positional parameters (also, x or y could be required).

How would that work for you?

@escamoteur
Copy link

Just a very simple example

  T get<T extends Object>({
    dynamic param,
    String? instanceName,
    Type? type,
  });

in 99% of the case, people don't pass any of these parameters with the exception, of the registered Type in GetIt is a factory. Then being able to pass a parameter is quite common and it would be nice to be about to write

GetIt.I<Myfactory>(valueTopassToTheFactory);
instead of 
GetIt.I<Myfactory>(param: valueTopassToTheFactory);

not sure if that would be possible with the linked proposal.

@eernstg
Copy link
Member

eernstg commented May 31, 2023

Thanks for the example, @escamoteur!

With a rough idea about the possible rules of a proposal according to this issue ('allow both positional and optional named parameters'), the example declaration might be expressed as follows:

T get<T extends Object>(
  [dynamic paramPositional], {
  dynamic param,
  String? instanceName,
  Type? type,
}) {
  param ??= paramPositional;
  ... // Remaining implementation unchanged.
}

void main() {
  var v = valueTopassToTheFactory;
  get<MyFactory>(v); // OK.
  get<MyFactory>(param: v); // OK.
  get<MyFactory>(v, param: v); // Bug: Pass both parameters, no error.
}

Pro: This is a mechanism that will work for statically checked invocations as well as invocations where the function has static type dynamic or Function, which is a very nice consistency property.

Con: There is no direct support for making a choice ("should we pass this argument by name or by position?"), which means that we have to declare two distinct parameters and use something like a default value of null and the ??= construct in order to use the parameter that actually received a useful value and ignore the other parameter. There is no support (in the language) for getting a diagnostic message if both parameters receive a non-trivial value by mistake.

With the 'optionally named parameter' proposal, we'd do as follows:

T get<T extends Object>({
  dynamic param?,
  String? instanceName,
  Type? type,
}) {
  // ... // Remaining implementation unchanged.
}

void main() {
  var v = valueTopassToTheFactory;
  get<MyFactory>(v); // OK.
  get<MyFactory>(param: v); // OK.
  get<MyFactory>(v, param: v); // Compile-time error.

  (get as dynamic)<MyFactory>(param: v); // OK.
  (get as dynamic)<MyFactory>(v); // Throws at run time.
}

Pro, at least for this example: We can express the property that it is the same parameter which is passed by name or positionally, and there is no danger that we're going to pass both of them by accident.

Con: This mechanism is compile-time-only, which means that the ability to pass a named parameter positionally does not work for dynamic invocations. However, it does work to pass it as a named parameter.

@JaffaKetchup
Copy link

If I'm honest, I'm not sure I like #831 ('optionally named parameter') to resolve this ('allow both positional and optional named parameters'). It in itself seems like an overcomplication and a fairly niche use-case feature - are there other languages that have this feature? As a resolution to this, it kind of feels like a low quality workaround.

Just as another example for this issue, adding named arguments to a method with existing unnamed positional arguments, without causing a breaking change.

@lrhn
Copy link
Member Author

lrhn commented May 31, 2023

C# has the "optionally named parameters" feature, in that they allow you to specify arguments either by position or by name. They're named arguments, not named parameters. C# does not have named (only) parameters.

@n7trd
Copy link

n7trd commented Oct 8, 2023

I would prefer removing optional positional parameters as it greatly simplifies things. #2232

@fercsi
Copy link

fercsi commented Jan 26, 2024

@JaffaKetchup, This feature is fairly often used in Python. Probably, the one who raised this request, use it too. The main reason for having both at the same time is, that the options you often use (but not always), you don't want to name. However, the rarely used options should be named for maintainability, but they often do not even have a position.

E.g. Using open you usually read a text file, so using open("myfile") is sufficient. However if you happen to write one, you use the positional parameter mode: open("myfile", "w"). But there are a bunch of further options, for which you must use names. Of course, this could be written, too: open("myfile", mode="w"). Note, that Python have 3 types of parameters all of which can be optional: positional only, named/keyword only, and those which can be used in position or with name (this latter is the default).

This can be a really strong feature if you get used to it.

@top-master
Copy link

top-master commented Nov 2, 2024

My use case is simple:

  • Two params are always used.
  • Two optional-params are sometimes used.
  • Two named-params are almost never used, knowing the name is the key to using them.

The name makes those last two omega-optionals.

However, since the language forbids it, now 4 are forced to have name, because, like if we pass true 2 times and false once, then who knows each goes to right param or not; specially if the 2 last params are almost never used.

@TekExplorer
Copy link

TekExplorer commented Nov 4, 2024

My use case is simple:

  • Two params are always used.
  • Two optional-params are sometimes used.
  • Two named-params are almost never used, knowing the name is the key to using them.

The name makes those last two omega-optionals.

However, since the language forbids it, now 4 are forced to have name, because, like if we pass true 2 times and false once, then who knows each goes to right param or not; specially if the 2 last params are almost never used.

Id consider that a bad API actually. Multiple positional booleans aren't clear what they're for or do. Imo; always name Boolean args. (Unless you're setting a Boolean value, maybe.)

@fercsi
Copy link

fercsi commented Nov 4, 2024

Whether a design is good or bad depends on its specific implementation. For example, I discussed Python's open function in the first post of this thread, which I consider a good design.

@aqdasak
Copy link

aqdasak commented Nov 7, 2024

Python's function definition is very clear in terms of required positional only, required keyword/named only, positional as well as named parameters just by using two operators "/" and "*".
What holds dart back to allow both optional positional and optional named arguments

@lrhn
Copy link
Member Author

lrhn commented Nov 7, 2024

There is nothing fundamental holding Dart back.
There needs to be a syntax for declaring both kinds of variables, but

void Foo(int x, [int? y], {int? z}) =>void Function(int, [int?], {int? z})

is the obvious choice.
Obviously subtyping and upper bounds would need to account for it, but the extension is trivial

No need for changes to call syntax.

The actual thing holding back this change is that it requires a change to fundamental parts of the implementation, the calling connection, where, fx, a function implemention cannot assume that named arguments always follow the same number of positional arguments.
Whether that is still a problem, and on which platforms, isn't clear. Have to ask each backend team whether they'll be able to handle the new argument calling.

That makes it a potentially expensive change, do other changes end up being chosen first.

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