Field | Value |
---|---|
DIP: | 1030 |
Review Count: | 2 |
Author: | Walter Bright walter@digitalmars.com |
Implementation: | |
Status: | Accepted |
Allow arguments in function calls to optionally be prefixed with the name of the function parameter to which they apply, analogous to the manner in which field names can optionally be used in struct initializers to prefix field initializers. This will enable better self documentation, make longer argument lists easier to review, enable reordering of arguments at the user's discretion, and no longer constrain default parameter values to the end of the parameter list.
- Rationale
- Prior Work
- Description
- Breaking Changes and Deprecations
- Reference
- Copyright & License
- Reviews
- Although this proposal supports reordering, the real reason for naming is so one can have a function with a longish list of parameters, each with a reasonable default, and users need only supply the arguments that matter for their use case. This is much more flexible than the current method of putting all the defaults at the end of the parameter list, and defaulting one means all the rest get defaulted.
- For a longish list of parameters, when users find themselves counting parameters to ensure they line up, then named parameters can be most helpful.
- A step towards making it possible to replace the brace struct initialization syntax with a function-call-like construct. I.e. the initialization of structs is unified with struct literals and struct constructors.
- Allow replacing
std.typecons.Flag
which is syntactically awkward. - For situations when code clarity at the caller site is of paramount importance (i.e. for some critical code), to make it unquestionably clear which values go where, even for short argument lists.
Both DIP1019 and DIP1020 detail prior work in other languages, which will not be repeated here.
This proposal is based on the recognition that D already has named parameters for struct initializers. Closely related are array initializers and associative array literals.
The syntax is modified to replace the definition of ArgumentList
for function call
arguments with:
ArgumentList:
- AssignExpression,
- AssignExpression,
- AssignExpression , ArgumentList
+ NamedArgument
+ NamedArgument ,
+ NamedArgument , ArgumentList
+NamedArgument:
+ Identifier : AssignExpression
+ AssignExpression
Assignment of arguments to parameters works the same as assignments to fields.
Matching of NamedArgument
s to a function's, function pointer's, or delegate's parameters
proceeds in lexical order.
For each NamedArgument
:
- If an
Identifier
is present, it matches theParameter
with the correspondingIdentifier
. If it does not match any namedParameter
, then it is an error. - If an
Identifier
is not present, theParameter
matched is the one following the previous matchedParameter
, or the firstParameter
if none have yet been matched. - Matching a
Parameter
more than once is an error. - After the
NamedArgumentList
is exhausted, any unmatchedParameter
s receive the corresponding default value specified for thatParameter
. If there is no default value, it is an error. - If there are more
NamedArgument
s thanParameter
s, the remainder match the trailing...
of variadic parameter lists, andIdentifier
s are not allowed.
The set of matched functions becomes the overload set, to which the usual overload resolution is applied to select the best match.
I.e., function resolution is done by constructing an argument list separately for each function before testing it for matching. If the parameter name(s) do not match, the function does not match. If a parameter has no corresponding argument, and no default value, then the function does not match.
void snoopy(T t, int i, S s); // A
void snoopy(S s, int i = 0, T t); // B
S s; T t; int i;
snoopy(t, i, s); // A
snoopy(s, i, t); // B
snoopy(s, t); // error, neither A nor B match
snoopy(t, s); // error, neither A nor B match
snoopy(s:s, t:t, i:i); // error, ambiguous
snoopy(s:s, t:t); // B
snoopy(t:t, s:s); // B
snoopy(t:t, i, s:s); // A
snoopy(s:s, t:t, i); // A
The AssignExpression
s are evaluated in the lexical order they appear in the
NamedArgumentList
.
There is no semantic difference between a.foo(b)
and foo(a, b)
when matching
named parameters to a function declaration. No Identifier
is possible for the a
argument
in the a.foo(b)
form.
The same is applied to Explicit Template Instantiation.
The TemplateArgumentList
is replaced with:
TemplateNamedArgumentList:
- TemplateArgument
- TemplateArgument ,
- TemplateArgument , TemplateArgumentList
+ TemplateNamedArgument
+ TemplateNamedArgument ,
+ TemplateNamedArgument , TemplateNamedArgumentList
+TemplateNamedArgument:
+ Identifier : TemplateArgument
+ TemplateArgument
The matching rules are the same as described for functions.
Since template arguments are evaluated at compile time, there is no order of evaluation consideration.
The parameter names in function declarations will become part of the API for the function, as changing them would break user code that made use of them. Hence, parameter names must be selected with care.
Parameter names will not be part of the mangled name of the function. This means that changing the parameter names will not break the binary API of the function.
Parameter names can be omitted from declarations if no function body is present:
int foo(int, double);
and the user will not be able to use named parameters when calling such functions.
There is no provision for preventing the caller from using named arguments when the parameters are named.
None
- DIP1020 Named Parameters ** Review Round 1 ** Review Round 2
- DIP1019 Named Arguments Lite ** Review Round 1 ** Review Round 2 ** Final Review
- DIP88 Named Arguments ** Review Thread Jan 23 2016 ** Any News on DIP88?
Copyright (c) 2019 by the D Language Foundation
Licensed under Creative Commons Zero 1.0
The following points were raised in the feedback thread:
- What happens with functions without parameter names, e.g.,
void foo(int, float)
? Shouldfoo(__parameter1:0, __parameter2:0.0f)
be accepted? The DIP author replied that since the parameters have no names, any attempt to call the function with names would result in "no match". - Does UFCS present a conflict, since the first parameter is already matched with the symbol to the left of the
.
? The DIP author replied that there is no semantic difference, e.g.,3.doStuff(6.7, b:"ham")
is an alternative todoStuff(3, 6.7, b:"ham")
. - How does the compiler handle function lookup when there is an ambiguous match, but the ambiguous function is in a different module?
- Questions about named arguments' interaction with variadic function and template parameters arose (also in the Discussion Thread). The DIP author said that it is clear that there is no certainty on how they should be resolved, and as such named arguments should simply be disallowed from matching with
...
so that it can be easily enabled if a future solution develops. - How does the feature work with forward declarations? The DIP author replied that D does not have forward declarations.
- Named arguments make function names part of the API, as changing function names beacomes a potentially breaking change. The DIP author said it's clear from the DIP that a call to
foo(x:3)
will fail to compile if there is nox
. He also said (in the Discussion Thread) that D already supports using member names in struct initializers, where the same issue can arise, but it has never come up as a problem. - The DIP should consider making the feature opt-in or opt-out, such as limiting named arguments to only match parameters with default values.
Although several pages worth of discussion took place in the Discussion Thread, no feedback was provided in the Feedback Thread.
The language maintainers approved this proposal.
They took this particular criticism under consideration:
Named arguments breaks this very important pattern:
auto wrapper(alias origFun)(Parameters!origFun args) { // special sauce return origFun(args); }
Their response was that, though it's true that Parameters!func
will not work in a wrapper, it "doesn't really work now"---default arguments and storage classes must be accounted for. This can be done with string mixins, or using a technique referred to by Jean-Louis Leroy as "refraction", both of which are clumsy. So they decided that a new std.traits
template and a corresponding __traits
option are needed which expand into the exact function signature of another function.
They also acknowledge that when an API's parameter names change, code depending on the old parameter names will break. Struct literals have the same problem and no one complains (the same is true for C99). And in any case, when such a change occurs, it's a hard failure as any code using named arguments with the old parameter names will fail to compile, making it easy to see how to resolve the issue. Given this, they find the benefits of the feature outweigh the potential for such breakage.