www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Named Arguments Status Update

reply Dennis <dkorpel gmail.com> writes:
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.


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.



```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`?



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?



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.



(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`.



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?
Jan 05
next sibling parent Dennis <dkorpel gmail.com> writes:
On Friday, 5 January 2024 at 09:48:53 UTC, Dennis wrote:
 implemented per [DIP 1030](dlang.org/dips/1030).
Corrected link: http://www.dlang.org/dips/1030
Jan 05
prev sibling next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 05/01/2024 10:48 PM, Dennis wrote:

 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`?
I would error on two fronts: 1. The alias sequence was empty 2. Too many arguments for parameters Clearly something was amiss when the programmer wrote it.

 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?
This seems like something that should have already been checked. But if not I suppose the smelly but correct solution is yes, try without named arguments using the parameter list found thanks to with named arguments. Otherwise can it not be continued checking? One iteration of checks, rather than 2. With only 1 allowed to match.

 Is the first parameter named `x`, `z`, both?
 Note: making the declaration of `h()` an error would be a breaking change.
I would go with z. As it is an aggregate. So the current behavior is correct. Otherwise it seems more surprise heavy than is required.

We currently do not support forwarding of any argument names. The only method we have is for alias template parameters and ``__traits(identifier, param)``. Language is lacking, until that is resolved named arguments shouldn't be trying to shoehorn it in. It needs a DIP if we want it.
Jan 05
parent reply Dennis <dkorpel gmail.com> writes:
On Friday, 5 January 2024 at 10:06:15 UTC, Richard (Rikki) Andrew 
Cattermole wrote:
 I would error on two fronts:
That's not how value sequences work today. ```D void f(int, int); f(AliasSeq!(), 1, 2); // ok, equal to f(1, 2) ```
 Clearly something was amiss when the programmer wrote it.
Consider generic code, where you could have a static array of generic size that you pass to a function with `.tupleof`. It should just work with a zero-size `int[0]` without special cases.
 This seems like something that should have already been checked.
The example is a simple case, but the template constraints may be arbitrarily complex. Consider: ```D string f(T : string)(T x) { return "x"; } string f(T )(T y) { return "y"; } ``` Now the original case with `T=int` works fine, but the problem re-emerges only with `T=string`.
 I would go with z. As it is an aggregate.
Type tuples are not aggregates currently.
 So the current behavior is correct.
Current behavior would name it `x`, not `z`.

Language is lacking, until that is resolved named arguments shouldn't be trying to shoehorn it in.
Agreed
Jan 05
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
Okay, so it seems at least as far as the context of my reply is 
concerned, we have nailed down that forwarding is out of scope of 
DIP1030 as that would be going beyond the scope of syntax sugar that is 
named arguments.

As for the rest, unfortunately they seem like judgement calls where you 
have to bring out the loosey goosey rules that only a production 
languages requires to refit stuff like this without breaking code.

Which I know isn't much help. Good work regardless!
Jan 05
prev sibling next sibling parent reply Dom DiSc <dominikus scherkl.de> writes:
On Friday, 5 January 2024 at 09:48:53 UTC, Dennis wrote:


 ```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)`.
I think this is correct. But I would strongly recommend not to use named arguments on a tuple, as the name will be assigned only to the first element of the tuple (if any) and everything beyond will depend on the tuple length - making it hard to find out to which parameters they go. This contradict the whole purpose of named arguments: to make clear to which parameter a value goes.


 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");
 ```
I call it a good thing that this doesn't work. This kind of feature-misuse is why I'm not a fan of named arguents. They are only acceptable in the most basic cases. Every more complex use of them is only obfuscating the code.


 You currently can't assign a tuple parameter by name:
Why should you? This doesn't help to make clear what value is used for what parameter (it's hidden behind the tuple anyway), so this is beside the usecase of named arguments.
 But consider that a type tuple can already have names when it 
 came from a parameter list:
Yeah. This is the place to use names, not in the call of the variadic args function.

Nope.

If you didn't give something a name in the function declaration, why should you at the call site? This whole thing becomes more of a burden than a feature.
Jan 05
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 1/5/24 15:01, Dom DiSc wrote:
 

If you didn't give something a name in the function declaration, why should you at the call site?
Perfect forwarding. Named arguments broke it.
Jan 05
prev sibling next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 1/5/24 10:48, Dennis wrote:
 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.
 

 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™.
 ...
Thank you a lot for all of your work on this!
 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.
 

 
 ```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`?
 ...
I think this should be an error because the type of `y` is `int`, it is not `AliasSeq!()`. To contrast, there is this related example (which type checks with DMD v2.106.0): ```d alias AliasSeq(T...)=T; void foo(T...)(int x,T y){} void main(){ foo!()(x:1,y:AliasSeq!()); } ``` Here the type of `y` is in fact `AliasSeq!()`, and so the value `AliasSeq!()` can be passed to it. I think either this should be made an error (in accordance with your elaboration on named sequence arguments below), or IFTI should similarly work: `foo(x:1,y:AliasSeq!())`.

 
 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?
 ...
I think it would be good if the cost of template instantiation did not double for overloaded templates with named arguments, but I think the optimized variant does not work because the two different instantiations may be in different compilation units and never coexist during the same compiler invocation.

 
 You currently can't assign a tuple parameter by name:
Slight correction: As I showed above, you can currently assign a sequence parameter by name just as long as it is empty. Maybe it would be good to remove this accidental feature for now.
 ```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.
 ...
I guess for now it would be best to disallow naming a sequence argument unless there are names in the parameter sequence.

 
 (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`.
 ...
I do think forwarding is important, but probably it will require another round of careful design as it was not considered in DIP1030. I think what matters at this point is that the implementation does not paint us into a design corner any more severely than what is already the case. Disabling named arguments for sequence arguments seems prudent and this restriction may be lifted later when we know precisely what to do about forwarding.

 
 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?
 
I am not sure what is good syntax for this. For the type case `AliasSeq!(int y,int x)` looks fine but `AliasSeq!(1 y,0 x)` seems weird. In general, I think this should be addressed together with forwarding.
Jan 05
prev sibling next sibling parent Paul Backus <snarwin gmail.com> writes:
On Friday, 5 January 2024 at 09:48:53 UTC, Dennis wrote:


 ```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`?
The analogous case for array initializers is currently an error: ```d alias AliasSeq(T...) = T; void main() { int[2] a = [1: AliasSeq!(), 1, 2]; // Error: cannot implicitly convert expression `()` of type `()` to `int` } ``` Whatever we decide here, these two cases should probably work the same way.
Jan 05
prev sibling next sibling parent zjh <fqbqrr 163.com> writes:
On Friday, 5 January 2024 at 09:48:53 UTC, Dennis wrote:
 Since dmd 2.103, named arguments for struct literals and 
 regular functions, including overloads, have been implemented 
 per [DIP 1030](dlang.org/dips/1030).
The purpose of `named parameters` is to solve the problem of the `location of default parameters`, so I think it is necessary to generate an `implicit name location correspondence` according to the `parameter name` of the function, and then automatically correspond to the corresponding location when calling. ```d string f(T)(T x) { return "x"; } string f(T)(T y) { return "y"; } ``` Here, the definition is conflicted!
Jan 05
prev sibling next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
Thanks for taking on the hard work of this!
Jan 08
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/5/2024 1:48 AM, Dennis wrote:

 
 ```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`?
My intuition says that an empty tuple is nothing at all, and should just be elided from consideration. Trying to assign nothing to parameter `y` doesn't make sense, and it should error. In fact, trying to assign a tuple to `y` that is anything other than one element should be an error. Timon knows more about tuples than I do, so his input would be most welcome.
Jan 08
next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 09/01/2024 12:52 PM, Walter Bright wrote:
 My intuition says that an empty tuple is nothing at all, and should just 
 be elided from consideration. Trying to assign nothing to parameter |y| 
 doesn't make sense, and it should error.
 
 In fact, trying to assign a tuple to |y| that is anything other than one 
 element should be an error.
 
 Timon knows more about tuples than I do, so his input would be most welcome.
In type theory a tuple can be: 1. A distinct type, it is an object, it is not a sum of its elements. 2. Is not a distinct type, it is not an object, it is the sum of its elements. 3. Can have named elements. 4. Cannot have named elements. 5. An element can be a type. Everything boils down to tuples, they are whatever a person needs it to be when doing the analysis. What we have now with alias sequences are not representative of tuples, although they are a subset of tuples.
Jan 08
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 1/9/24 00:52, Walter Bright wrote:
 On 1/5/2024 1:48 AM, Dennis wrote:


 ```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`?
My intuition says that an empty tuple is nothing at all, and should just be elided from consideration. Trying to assign nothing to parameter `y` doesn't make sense, and it should error. In fact, trying to assign a tuple to `y` that is anything other than one element should be an error. Timon knows more about tuples than I do, so his input would be most welcome.
I fully agree with your analysis above. This should error.
Jan 09
next sibling parent reply Max Samukha <maxsamukha gmail.com> writes:
On Tuesday, 9 January 2024 at 13:25:49 UTC, Timon Gehr wrote:

 I fully agree with your analysis above. This should error.
AliasSeq!() - point A AliasSeq!(1, 2) - interval B Concatenate A and B, and you'll get B, but the point is still there, and it has a name, despite the fact it is "nothing". If we make that an error, we should make `AliasSeq!() x;` an error too, which would be unfortunate.
Jan 09
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 1/9/24 16:40, Max Samukha wrote:
 On Tuesday, 9 January 2024 at 13:25:49 UTC, Timon Gehr wrote:
 
 I fully agree with your analysis above. This should error.
AliasSeq!() - point A AliasSeq!(1, 2) - interval B Concatenate A and B, and you'll get B, but the point is still there, and it has a name, despite the fact it is "nothing". If we make that an error, we should make `AliasSeq!() x;` an error too, which would be unfortunate.
No. In that example `y` is an `int`.
Jan 09
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/9/2024 5:25 AM, Timon Gehr wrote:
 I fully agree with your analysis above. This should error.
I will treasure this day forever. Timon agrees with me!
Jan 11
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 1/11/24 20:13, Walter Bright wrote:
 On 1/9/2024 5:25 AM, Timon Gehr wrote:
 I fully agree with your analysis above. This should error.
I will treasure this day forever. Timon agrees with me!
Well, that is a regular occurrence, it's why I am a user of D.
Jan 11
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/5/2024 1:48 AM, Dennis wrote:

 
 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?
``` string f(T)(T x) { return "x"; } string f(T)(T y) { return "y"; } ``` should give an error. The order should not matter. Just like: ``` string f(int x) { return "x"; } string f(int y) { return "y"; } ``` gives this error: Error: function `test3.f(int y)` conflicts with previous declaration at test3.d(2) I.e. as you correctly observed, the function/template signature is not affected by the parameter names, therefore two functions/templates definitions with the same signature are an error.
Jan 08
parent reply Dennis <dkorpel gmail.com> writes:
On Tuesday, 9 January 2024 at 00:08:06 UTC, Walter Bright wrote:
 ```
 string f(T)(T x) { return "x"; }
 string f(T)(T y) { return "y"; }
 ```

 should give an error. The order should not matter.
 (...)
 the function/template signature is not affected by the 
 parameter names, therefore two functions/templates definitions 
 with the same signature are an error.
Depending on what you consider the 'signature', that's either not sufficient reason to raise an ambiguity error, or not sufficient prevention of conflicts. Consider that the same 'signature' can be distinguished by constraints: ```D string f(T)(T x) if (T.sizeof <= 2) { return "x"; } string f(T)(T y) if (T.sizeof >= 2) { return "y"; } ``` This is allowed and works like this: ```D pragma(msg, f(byte(0))) // x pragma(msg, f(int(0))) // y pragma(msg, f(short(0))) // error, `(short)` matches both templates ``` But with named arguments: ```D pragma(msg, f(x: short(0))) // x, `(short)` matches x pragma(msg, f(y: short(0))) // y, `(short)` matches y ``` Detecting potential overlap upfront is both a breaking change and mathematically impossible.
Jan 11
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/11/2024 2:40 AM, Dennis wrote:
 Depending on what you consider the 'signature', that's either not sufficient 
 reason to raise an ambiguity error, or not sufficient prevention of conflicts. 
 Consider that the same 'signature' can be distinguished by constraints:
 
 ```D
 string f(T)(T x) if (T.sizeof <= 2) { return "x"; }
 string f(T)(T y) if (T.sizeof >= 2) { return "y"; }
 ```
 
 This is allowed and works like this:
 ```D
 pragma(msg, f(byte(0)))  // x
 pragma(msg, f(int(0)))   // y
 pragma(msg, f(short(0))) // error, `(short)` matches both templates
 ```
 
 But with named arguments:
 
 ```D
 pragma(msg, f(x: short(0))) // x, `(short)` matches x
 pragma(msg, f(y: short(0))) // y, `(short)` matches y
 ```
 
 Detecting potential overlap upfront is both a breaking change and
mathematically 
 impossible.
It's ok if the error is detected after instantiation. It can be detected by testing to see if the mangled signature (which is generated for the type of the function) already exists. The signature does not include parameter names nor the constraints. There must be a 1:1 correspondence between symbols and signature. The function's type is also its signature. If these invariants do not hold, or if we make exceptions for them, the whole type system and assumptions about D fall apart in an unfixable manner.
Jan 11
next sibling parent reply Dennis <dkorpel gmail.com> writes:
On Thursday, 11 January 2024 at 19:21:46 UTC, Walter Bright wrote:
 It's ok if the error is detected after instantiation. It can be 
 detected by testing to see if the mangled signature (which is 
 generated for the type of the function) already exists.
That is the second option I listed in my opening post, which can be implemented easily. However, it would create a compile time 'race condition': the `f!short` instantiation which dmd sees first may succeed, but any subsequent attempts will fail. I don't know if this will result in inscrutable errors in practice, but it very well may.
Jan 11
parent Walter Bright <newshound2 digitalmars.com> writes:
On 1/11/2024 12:33 PM, Dennis wrote:
 On Thursday, 11 January 2024 at 19:21:46 UTC, Walter Bright wrote:
 It's ok if the error is detected after instantiation. It can be detected by 
 testing to see if the mangled signature (which is generated for the type of 
 the function) already exists.
That is the second option I listed in my opening post, which can be implemented easily. However, it would create a compile time 'race condition': the `f!short` instantiation which dmd sees first may succeed, but any subsequent attempts will fail. I don't know if this will result in inscrutable errors in practice, but it very well may.
string f(T)(T x) if (T.sizeof <= 2) { return "x"; } string f(T)(T y) if (T.sizeof >= 2) { return "y"; } Then the only thing to do is disallow an overload that differs only in the parameter names.
Jan 14
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 1/11/24 20:21, Walter Bright wrote:
 
 It's ok if the error is detected after instantiation. It can be detected 
 by testing to see if the mangled signature (which is generated for the 
 type of the function) already exists.
As I wrote in another post, I do not think this works, because one instantiation may be in one compilation unit while the other instantiation is in another compilation unit. They never exist both at once in the same compiler invocation but they will have the same mangled name yet be different.
Jan 11
prev sibling next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 1/5/2024 1:48 AM, Dennis wrote:

 
 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);
 ```
It should fail, unless the AliasSeq has exactly one (unnamed) member, when it is clear what should happen.
 I can change it so it expands to
 ```D
 int f(int x, int __param_1)
 ```
We shouldn't be inventing new behavior without a valuable use case. Corner cases like this should just be an error. That leaves us open to making it work if a valuable use case emerges, rather than discovering we are stuck with wrong behavior.
 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.
Trying to rename a parameter when the tuple element already has a parameter name should be an error. In general, questionable corner cases should be treated as errors until a compelling use case for them is found.
Jan 08
prev sibling next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 1/5/2024 1:48 AM, Dennis wrote:

 
 (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!
I would say no, they do not propagate to the parameter. I expect it would confuse the hell out of people.
 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`.
I'd go a step further. Disallow named arguments being passed as a variadic argument list. We can always turn it on later if some compelling reason turns up.
Jan 08
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 1/5/2024 1:48 AM, Dennis wrote:

 
 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?
Make that an error and leave it to some future design.
Jan 08