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 · 46 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
@lrhn
Copy link
Member Author

lrhn commented May 25, 2022

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.

@FMorschel
Copy link

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.

@munificent
Copy link
Member

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.

@nate-thegrate
Copy link

nate-thegrate commented Jun 3, 2022

I think it's worth noting that there's some nice synergy between this issue and the new Dart 2.17 features.

Since we now have "named arguments anywhere", we could eventually change child and children into positional arguments:

Container(
  color: Colors.green,
  Column(
    mainAxisSize: MainAxisSize.min,
    [
      Text('Elegant as heck'),
    ],
  ),
)

Combining optional positional & named arguments can also be helpful when using super initializers:

class CustomBox extends StatelessWidget {
  final int? height;
  const CustomBox([this.height, Key? key]) : super(key: key); // the old way to do it

  const CustomBox([this.height, super.key]);   // throws an error
  const CustomBox([this.height], {super.key}); // it would be great if this worked!
  ...
}

It's unfortunate to hear that this issue affects Dart's calling convention. Guess I'll just keep my fingers crossed that it'll work out!

@lrhn lrhn mentioned this issue Sep 5, 2022
anxix added a commit to anxix/gps_history that referenced this issue Sep 20, 2022
…dicate if they want autoclamping, instead of only the GpsPoint.clamped constructor.

This required changing the signature of the fromUtc constructor to use keyword parameters instead of optional positional ones, as the two types cannot be combined, see dart-lang/language#1076 .
@lrhn lrhn closed this as completed Feb 13, 2023
@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.

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