digitalmars.D - Named Arguments Status Update
- Dennis (130/130) Jan 05 Since dmd 2.103, named arguments for struct literals and regular
- Dennis (3/4) Jan 05 Corrected link:
- Richard (Rikki) Andrew Cattermole (19/33) Jan 05 I would error on two fronts:
- Dennis (21/29) Jan 05 That's not how value sequences work today.
- Richard (Rikki) Andrew Cattermole (8/8) Jan 05 Okay, so it seems at least as far as the context of my reply is
- Dom DiSc (23/47) Jan 05 I think this is correct.
- Timon Gehr (2/7) Jan 05 Perfect forwarding. Named arguments broke it.
- Timon Gehr (39/198) Jan 05 I think this should be an error because the type of `y` is `int`, it is
- Paul Backus (13/24) Jan 05 The analogous case for array initializers is currently an error:
- zjh (11/14) Jan 05 The purpose of `named parameters` is to solve the problem of the
- Walter Bright (1/1) Jan 08 Thanks for taking on the hard work of this!
- Walter Bright (7/21) Jan 08 My intuition says that an empty tuple is nothing at all, and should just...
- Richard (Rikki) Andrew Cattermole (12/20) Jan 08 In type theory a tuple can be:
- Timon Gehr (2/28) Jan 09 I fully agree with your analysis above. This should error.
- Max Samukha (7/8) Jan 09 AliasSeq!() - point A
- Timon Gehr (2/14) Jan 09 No. In that example `y` is an `int`.
- Walter Bright (2/3) Jan 11 I will treasure this day forever. Timon agrees with me!
- Timon Gehr (2/7) Jan 11 Well, that is a regular occurrence, it's why I am a user of D.
- Walter Bright (15/43) Jan 08 ```
- Dennis (23/32) Jan 11 Depending on what you consider the 'signature', that's either not
- Walter Bright (9/34) Jan 11 It's ok if the error is detected after instantiation. It can be detected...
- Dennis (8/11) Jan 11 That is the second option I listed in my opening post, which can
- Walter Bright (5/18) Jan 14 string f(T)(T x) if (T.sizeof <= 2) { return "x"; }
- Timon Gehr (6/10) Jan 11 As I wrote in another post, I do not think this works, because one
- Walter Bright (10/43) Jan 08 It should fail, unless the AliasSeq has exactly one (unnamed) member, wh...
- Walter Bright (5/32) Jan 08 I would say no, they do not propagate to the parameter. I expect it woul...
- Walter Bright (2/15) Jan 08 Make that an error and leave it to some future design.
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
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
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
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`.AgreedLanguage is lacking, until that is resolved named arguments shouldn't be trying to shoehorn it in.
Jan 05
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
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
On 1/5/24 15:01, Dom DiSc wrote:Perfect forwarding. Named arguments broke it.If you didn't give something a name in the function declaration, why should you at the call site?
Jan 05
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
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
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
Thanks for taking on the hard work of this!
Jan 08
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
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
On 1/9/24 00:52, Walter Bright wrote:On 1/5/2024 1:48 AM, Dennis wrote:I fully agree with your analysis above. This should error.```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 09
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
On 1/9/24 16:40, Max Samukha wrote:On Tuesday, 9 January 2024 at 13:25:49 UTC, Timon Gehr wrote:No. In that example `y` is an `int`.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
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
On 1/11/24 20:13, Walter Bright wrote:On 1/9/2024 5:25 AM, Timon Gehr wrote:Well, that is a regular occurrence, it's why I am a user of D.I fully agree with your analysis above. This should error.I will treasure this day forever. Timon agrees with me!
Jan 11
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
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
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
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
On 1/11/2024 12:33 PM, Dennis wrote:On Thursday, 11 January 2024 at 19:21:46 UTC, Walter Bright wrote: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.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 14
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
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
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
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