Named Arguments Status Update
Dennis
dkorpel at gmail.com
Fri Jan 5 09:48:53 UTC 2024
Since dmd 2.103, named arguments for struct literals and regular
functions, including overloads, have been implemented per [DIP
1030](dlang.org/dips/1030). Making it work with template
functions turned out to be a bit more difficult than expected, so
had to be pushed back to a later release. I know people don't
like half-baked / unfinished features, so I didn't want to
announce it yet by adding it to the changelog. I considered
introducing a `-preview=namedArguments` switch, but then that
switch would quickly linger in a deprecated state, and dub
packages may need to conditionally specify that switch to support
both dmd 2.103 and newer releases. That's why I thought it'd
would be simpler to silently let it sit in the compiler, but in
retrospect, it ended up causing confusion (example: [issue
24241](https://issues.dlang.org/show_bug.cgi?id=24241)), so I
won't do this again if there's a next time.
## Progress
You can see the state of the named arguments implementation [on
its projects page](https://github.com/orgs/dlang/projects/19).
I've been meaning to finish at least named function arguments (as
opposed to named template arguments) before the end of 2023, but
fell short unfortunately.
Templates got me stuck for a while because of a circular
dependency between parameter types (which can be tuples) and
argument assignments:
- The function to resolve named arguments needs a function
signature.
- The function signature is created by deducing template
arguments.
- Template arguments are deduced by (named) function arguments
The good news is: I found a solution that I'm satisfied with, and
have a [working Pull
Request](https://github.com/dlang/dmd/pull/15040) to merge Soon™.
However, while implementing all of this, I did encounter various
ambiguities / edge cases which weren't covered by DIP 1030's text
that could use your input.
## Empty tuple value
```D
alias AliasSeq(T...) = T;
int f(int x, int y) { return 0; }
int v = f(y: AliasSeq!(), 1, 2);
```
Currently, the named argument y with an empty tuple will collapse
into nothing, and `(1, 2)` will be assigned to `(x, y)`.
- Should this be an error?
- Should this assign `1` to `y`?
## Overloading by name
With named arguments, you can disambiguate an overload with
identical types by name:
```D
string f(T)(T x) { return "x"; }
string f(T)(T y) { return "y"; }
static assert(f(x: 0) == "x");
static assert(f(y: 0) == "y");
```
However, both template functions will end up with exactly the
same types. DIP 1030 specifies parameter names aren't part of the
mangling, resulting in clashing symbols at run time:
```D
void main()
{
writeln(f(x: 1)); // x
writeln(f(y: 1)); // also x
}
```
Should the compiler, after finding a matching overload, retry all
other overloads without named arguments to prevent this? Or
should it instantiate it the `x` variant because it saw it first,
and then refuse to instantiate `y` because the mangle has been
seen before?
## Tuple parameters
You currently can't assign a tuple parameter by name:
```D
alias AliasSeq(T...) = T;
int f(AliasSeq!(int, int) x) { return 0; }
// This will expand to:
// int f(int __param_0, int __param_1) { return 0; }
// So this fails:
int v = f(x: 1, 2);
```
I can change it so it expands to
```D
int f(int x, int __param_1)
```
But consider that a type tuple can already have names when it
came from a parameter list:
```D
int f(int x, int y) { return 0; }
static if (is(typeof(f) T == __parameters)) {}
pragma(msg, T); // (int x, int y)
int g(T) {return 0;}
static assert(g(x: 3, y: 5) == 0); // Currently works
int h(T z) {return 0;}
static assert(h(z: 3, 5) == 0); // Fails, should this work?
```
Is the first parameter named `x`, `z`, both?
Note: making the declaration of `h()` an error would be a
breaking change.
## Forwarding?
(This did not come up in the implementation, but was pointed out
by Timon Gehr on Discord.)
Is there a way to forward named arguments? Consider:
```D
import std.stdio;
int f(int x, int y);
auto logAndCall(alias f, T...)(T args)
{
writeln(args);
return f(args);
}
logAndCall!f(y: 1, x: 0);
```
Are the names propagated to the `T args` parameter? If so, that
wouldn't be hygienic:
Imagine an argument named `writeln` - it would hijack the
function call!
Perhaps we could allow access to names some other way, like
`args.x`. But still, if we had another parameter `(T args, string
file)` then the called function could not have a parameter named
`file`.
## Named value sequence?
So if we can't implicitly give a `T...` names, can we explicitly?
We already saw a `__parameters` type tuple can have names, this
could be expanded to value sequences:
```D
logAndCall!f(args: AliasSeq!(y: 1, x: 0));
```
This syntax is ambiguous with named template parameters however:
According to DIP 1030, this should try to set template parameters
`y` and `x` of the `AliasSeq` template. Is there a way to make
forwarding named arguments work?
More information about the Digitalmars-d
mailing list