www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Struct Initialization syntax

reply Seb <seb wilzba.ch> writes:
tl;dr: the currently proposed syntax options are:


---
struct S
{
     int a = 2, b = 4, c = 6;
}
void foo()
{
     bar(S({c: 10})); // Option 1
     bar(S(c: 10));   // Option 2
     bar(S{c: 10});   // Option 3
}
---

So the struct-initialization DIP has been stalled for too long 
and I think it's time we finally get this story done.
I personally prefer option 2, but this might be in conflict to 
named arguments which we hopefully see in the near future too. 
Hence, I'm leaning forward to proposing Option 1 as the 
recommended Option for the DIP (that's also what the PoC DMD PR 
implements). What's your take on this?

DIP: https://github.com/dlang/DIPs/pull/71
Rendered view: 
https://github.com/wilzbach/DIPs/blob/struct-initialization/DIPs/DIP1xxx-sw.md
Jul 23 2018
next sibling parent =?UTF-8?B?THXDrXM=?= Marques <luis luismarques.eu> writes:
On Monday, 23 July 2018 at 16:26:42 UTC, Seb wrote:
 Hence, I'm leaning forward to proposing Option 1 as the 
 recommended Option for the DIP (that's also what the PoC DMD PR 
 implements). What's your take on this?
Although a bit more verbose that seems like a good choice. If you could save the “initialization list” in an enum that choice also seems to generalize better. Not conflicting with named arguments is important for me, as named arguments would be very nice to have for hardware description kind of stuff.
Jul 23 2018
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Jul 23, 2018 at 04:26:42PM +0000, Seb via Digitalmars-d wrote:
 tl;dr: the currently proposed syntax options are:
 ---
 struct S
 {
     int a = 2, b = 4, c = 6;
 }
 void foo()
 {
     bar(S({c: 10})); // Option 1
     bar(S(c: 10));   // Option 2
     bar(S{c: 10});   // Option 3
 }
 ---
 
 So the struct-initialization DIP has been stalled for too long and I
 think it's time we finally get this story done.
+1.
 I personally prefer option 2, but this might be in conflict to named
 arguments which we hopefully see in the near future too.
Yeah.
 Hence, I'm leaning forward to proposing Option 1 as the recommended
 Option for the DIP (that's also what the PoC DMD PR implements).
 What's your take on this?
[...] I don't like option 1 because it resembles anonymous function syntax and AA initialization syntax, but is actually neither. I'm on the fence about option 2 and option 3. I actually prefer option 3 as being overtly special initialization syntax that doesn't try to masquerade as something else. But OTOH, option 2 has a lot going for it, given that today, S(x, y, z) is the syntax for initializing struct fields in order, so one would expect that S(p: x, q: y, r: z) ought to be a natural extension of the syntax for specifying fields out-of-order (or with some fields omitted). It's true that there's potential conflict with named arguments, but IMO it's a mighty bad idea to name ctor parameters in a way that conflicts with the struct fields. Either your struct has a member named x, your ctor uses parameter names that are different from x (thus avoiding the confusion), or if your ctor also takes a parameter named x, in which case one would expect that it would initialize the member x, as opposed to a different member y. To have a ctor take a parameter named x but using it to initialize member y instead of member x, seems to be such a horrible idea that it should not be a big deal for struct initialization syntax to "conflict" with it. T -- If you want to solve a problem, you need to address its root cause, not just its symptoms. Otherwise it's like treating cancer with Tylenol...
Jul 23 2018
next sibling parent reply aliak <something something.com> writes:
On Monday, 23 July 2018 at 16:57:20 UTC, H. S. Teoh wrote:

 It's true that there's potential conflict with named arguments, 
 but IMO it's a mighty bad idea to name ctor parameters in a way 
 that conflicts with the struct fields.
After using Swift for a while, I've found this to be the exact opposite. It is on the contrary very common to name parameters as you would your members because they are very commonly the most intuitive names. struct Point { int x, int y; this(x: int, y: int) {} } auto p = Point(x: 3, y: 4); // what else would you name them? Can we just consider that named struct init syntax *is* a generated named constructor? If named arguments choose a different syntax then you have no conflict. If they go with the same (i.e. option 2) then you have seamless consistency. Cheers, - Ali
Jul 23 2018
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Jul 23, 2018 at 05:32:23PM +0000, aliak via Digitalmars-d wrote:
 On Monday, 23 July 2018 at 16:57:20 UTC, H. S. Teoh wrote:
 
 It's true that there's potential conflict with named arguments, but
 IMO it's a mighty bad idea to name ctor parameters in a way that
 conflicts with the struct fields.
After using Swift for a while, I've found this to be the exact opposite. It is on the contrary very common to name parameters as you would your members because they are very commonly the most intuitive names.
[...] I worded myself poorly. What I meant was that if a ctor parameter has the same name as a field, then it's obviously meant to initialize that field, so there isn't really a conflict, you're just passing the argument to the ctor instead of setting it directly to the struct. It would be a horrendously bad idea to have a ctor parameter that has the same name as a field, but is used to initialize a different field. OTOH, if you have a ctor, then one would assume that you're intentionally overriding direct initialization of fields, so you wouldn't want people to be using named initialization of fields anyway, they should be using the ctor instead. So any conflicts in this area wouldn't really be relevant.
 struct Point {
   int x, int y;
   this(x: int, y: int) {}
 }
 
 auto p = Point(x: 3, y: 4); // what else would you name them?
 
 Can we just consider that named struct init syntax *is* a generated
 named constructor?
 
 If named arguments choose a different syntax then you have no
 conflict. If they go with the same (i.e. option 2) then you have
 seamless consistency.
[...] Yes, this is what I was trying to get at. Thanks! T -- Don't modify spaghetti code unless you can eat the consequences.
Jul 23 2018
next sibling parent aliak <something something.com> writes:
On Monday, 23 July 2018 at 17:46:12 UTC, H. S. Teoh wrote:
 I worded myself poorly.  What I meant was that if a ctor 
 parameter has the same name as a field, then it's obviously 
 meant to initialize that field, so there isn't really a 
 conflict, you're just passing the argument to the ctor instead 
 of setting it directly to the struct.  It would be a 
 horrendously bad idea to have a ctor parameter that has the 
 same name as a field, but is used to initialize a different 
 field.
 [...]
 
 If named arguments choose a different syntax then you have no 
 conflict. If they go with the same (i.e. option 2) then you 
 have seamless consistency.
[...] Yes, this is what I was trying to get at. Thanks! T
Hehe oops, indeed you were :p Seems I did not parse properly. Apologies! Cheers, - Ali
Jul 23 2018
prev sibling parent =?UTF-8?B?THXDrXM=?= Marques <luis luismarques.eu> writes:
On Monday, 23 July 2018 at 17:46:12 UTC, H. S. Teoh wrote:
 Yes, this is what I was trying to get at.  Thanks!
If such integration was possible then that sounds like it would be a good solution.
Jul 23 2018
prev sibling parent reply kinke <noone nowhere.com> writes:
On Monday, 23 July 2018 at 17:32:23 UTC, aliak wrote:
 Can we just consider that named struct init syntax *is* a 
 generated named constructor?

 If named arguments choose a different syntax then you have no 
 conflict. If they go with the same (i.e. option 2) then you 
 have seamless consistency.
+1. And hoping for the latter, seamless consistency.
Jul 23 2018
parent reply rikki cattermole <rikki cattermole.co.nz> writes:
On 24/07/2018 6:43 AM, kinke wrote:
 On Monday, 23 July 2018 at 17:32:23 UTC, aliak wrote:
 Can we just consider that named struct init syntax *is* a generated 
 named constructor?

 If named arguments choose a different syntax then you have no 
 conflict. If they go with the same (i.e. option 2) then you have 
 seamless consistency.
+1. And hoping for the latter, seamless consistency.
Based upon my DIP that is in the queue for named arguments, it would be trivial for this DIP to make it so a named parameter constructor can override the default behavior and I think that this is the best way forward.
Jul 23 2018
parent reply Daniel N <no public.email> writes:
On Tuesday, 24 July 2018 at 03:59:53 UTC, rikki cattermole wrote:
 On 24/07/2018 6:43 AM, kinke wrote:
 On Monday, 23 July 2018 at 17:32:23 UTC, aliak wrote:
 Can we just consider that named struct init syntax *is* a 
 generated named constructor?

 If named arguments choose a different syntax then you have no 
 conflict. If they go with the same (i.e. option 2) then you 
 have seamless consistency.
+1. And hoping for the latter, seamless consistency.
Based upon my DIP that is in the queue for named arguments, it would be trivial for this DIP to make it so a named parameter constructor can override the default behavior and I think that this is the best way forward.
Yes, it makes sense to review the "named arguments" DIP before this one, any link?
Jul 24 2018
parent rikki cattermole <rikki cattermole.co.nz> writes:
On 24/07/2018 7:23 PM, Daniel N wrote:
 On Tuesday, 24 July 2018 at 03:59:53 UTC, rikki cattermole wrote:
 On 24/07/2018 6:43 AM, kinke wrote:
 On Monday, 23 July 2018 at 17:32:23 UTC, aliak wrote:
 Can we just consider that named struct init syntax *is* a generated 
 named constructor?

 If named arguments choose a different syntax then you have no 
 conflict. If they go with the same (i.e. option 2) then you have 
 seamless consistency.
+1. And hoping for the latter, seamless consistency.
Based upon my DIP that is in the queue for named arguments, it would be trivial for this DIP to make it so a named parameter constructor can override the default behavior and I think that this is the best way forward.
Yes, it makes sense to review the "named arguments" DIP before this one, any link?
Mine: https://github.com/dlang/DIPs/pull/126 Yshui's: https://github.com/dlang/DIPs/pull/123 Mine is a lot heavier-weight, and applies to templates parameters as well as functions. Where as Yshui's is very lightweight compared and only applies to functions. Yshui's will occur first I think, but really we should review them together to decide what sort of approach is best.
Jul 24 2018
prev sibling parent reply John Colvin <john.loughran.colvin gmail.com> writes:
On Monday, 23 July 2018 at 16:57:20 UTC, H. S. Teoh wrote:
 On Mon, Jul 23, 2018 at 04:26:42PM +0000, Seb via Digitalmars-d 
 wrote:
 tl;dr: the currently proposed syntax options are:
 ---
 struct S
 {
     int a = 2, b = 4, c = 6;
 }
 void foo()
 {
     bar(S({c: 10})); // Option 1
     bar(S(c: 10));   // Option 2
     bar(S{c: 10});   // Option 3
 }
 ---
 
 So the struct-initialization DIP has been stalled for too long 
 and I think it's time we finally get this story done.
+1.
 I personally prefer option 2, but this might be in conflict to 
 named arguments which we hopefully see in the near future too.
Yeah.
 Hence, I'm leaning forward to proposing Option 1 as the 
 recommended Option for the DIP (that's also what the PoC DMD 
 PR implements). What's your take on this?
[...] I don't like option 1 because it resembles anonymous function syntax and AA initialization syntax, but is actually neither.
Seeing as we already have S s = { c : 10 }; I'd say it would be fairer to say it resembles anonymous function syntax and AA initialisation syntax, but mostly it resembles the existing struct initialisation syntax.
Jul 23 2018
parent Guillaume Lathoud <gsub glat.info> writes:
On Monday, 23 July 2018 at 18:31:03 UTC, John Colvin wrote:
 On Monday, 23 July 2018 at 16:57:20 UTC, H. S. Teoh wrote:
 On Mon, Jul 23, 2018 at 04:26:42PM +0000, Seb via 
 Digitalmars-d wrote:
 tl;dr: the currently proposed syntax options are:
 ---
 struct S
 {
     int a = 2, b = 4, c = 6;
 }
 void foo()
 {
     bar(S({c: 10})); // Option 1
     bar(S(c: 10));   // Option 2
     bar(S{c: 10});   // Option 3
 }
... Seeing as we already have S s = { c : 10 }; I'd say it would be fairer to say it resembles anonymous function syntax and AA initialisation syntax, but mostly it resembles the existing struct initialisation syntax.
Not sure about this, but wouldn't bar({c: 10}); be consistent with S s = { c : 10 }; ?
Jul 24 2018
prev sibling next sibling parent reply Cym13 <cpicard openmailbox.org> writes:
On Monday, 23 July 2018 at 16:26:42 UTC, Seb wrote:
 tl;dr: the currently proposed syntax options are:


 ---
 struct S
 {
     int a = 2, b = 4, c = 6;
 }
 void foo()
 {
     bar(S({c: 10})); // Option 1
     bar(S(c: 10));   // Option 2
     bar(S{c: 10});   // Option 3
 }
 ---

 So the struct-initialization DIP has been stalled for too long 
 and I think it's time we finally get this story done.
 I personally prefer option 2, but this might be in conflict to 
 named arguments which we hopefully see in the near future too. 
 Hence, I'm leaning forward to proposing Option 1 as the 
 recommended Option for the DIP (that's also what the PoC DMD PR 
 implements). What's your take on this?

 DIP: https://github.com/dlang/DIPs/pull/71
 Rendered view: 
 https://github.com/wilzbach/DIPs/blob/struct-initialization/DIPs/DIP1xxx-sw.md
I'm in favour of 3. Option 2 looks nice but I'm against it because of possible named arguments. Even though they're not part of the language yet and may never be we already have too many clunky things not to avoid a conflict when we can. Option 1 is clean but a bit strange, I don't like the idea of doubling the enclosing symbols, in that situation you'd expect S() and {} to have separate effects, not to combine into a special effect. It also looks like a constructor while it's not, which isn't a nice conflation to make. I wouldn't be very dismayed by it though. Still, that's why I prefer option 3 which is very similar to classical struct initialization and has clearly only one effect. PS: Now that I think about it, would something like S{c:3}("a") be allowed to say “Call the constructor with the string "a" as argument on the struct of type S initialized with c=3”? I may have missed it but I don't think that's addressed by the DIP.
Jul 23 2018
parent Cym13 <cpicard openmailbox.org> writes:
On Monday, 23 July 2018 at 17:10:08 UTC, Cym13 wrote:
 PS: Now that I think about it, would something like S{c:3}("a") 
 be allowed to say “Call the constructor with the string "a" as 
 argument on the struct of type S initialized with c=3”? I may 
 have missed it but I don't think that's addressed by the DIP.
I took that last point to GH, no need to discuss it here.
Jul 23 2018
prev sibling next sibling parent reply Andre Pany <andre s-e-a-p.de> writes:
On Monday, 23 July 2018 at 16:26:42 UTC, Seb wrote:
 tl;dr: the currently proposed syntax options are:


 ---
 struct S
 {
     int a = 2, b = 4, c = 6;
 }
 void foo()
 {
     bar(S({c: 10})); // Option 1
     bar(S(c: 10));   // Option 2
     bar(S{c: 10});   // Option 3
 }
 ---

 So the struct-initialization DIP has been stalled for too long 
 and I think it's time we finally get this story done.
 I personally prefer option 2, but this might be in conflict to 
 named arguments which we hopefully see in the near future too. 
 Hence, I'm leaning forward to proposing Option 1 as the 
 recommended Option for the DIP (that's also what the PoC DMD PR 
 implements). What's your take on this?

 DIP: https://github.com/dlang/DIPs/pull/71
 Rendered view: 
 https://github.com/wilzbach/DIPs/blob/struct-initialization/DIPs/DIP1xxx-sw.md
I also prefer option 3. From a readability point of view it is the most pleasant one (my personal opinion). I also like it due to the already existing struct initialization syntax I am using a lot. Kind regards Andre
Jul 23 2018
parent JohnB <johnb johnb.com> writes:
On Monday, 23 July 2018 at 18:02:04 UTC, Andre Pany wrote:
 I also prefer option 3. From a readability point of view it is 
 the most pleasant one (my personal opinion). I also like it due 
 to the already existing struct initialization syntax I am using 
 a lot.
+1. JohnB.
Jul 23 2018
prev sibling next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2018-07-23 18:26, Seb wrote:
 tl;dr: the currently proposed syntax options are:
 
 
 ---
 struct S
 {
      int a = 2, b = 4, c = 6;
 }
 void foo()
 {
      bar(S({c: 10})); // Option 1
      bar(S(c: 10));   // Option 2
      bar(S{c: 10});   // Option 3
 }
 ---
 
 So the struct-initialization DIP has been stalled for too long and I 
 think it's time we finally get this story done.
 I personally prefer option 2, but this might be in conflict to named 
 arguments which we hopefully see in the near future too. Hence, I'm 
 leaning forward to proposing Option 1 as the recommended Option for the 
 DIP (that's also what the PoC DMD PR implements). What's your take on this?
 
 DIP: https://github.com/dlang/DIPs/pull/71
 Rendered view: 
 https://github.com/wilzbach/DIPs/blob/struct-initialization/DIPs/DIP1xxx-sw.md 
Talking about future potential features, Option 1 could be in conflict with a tuple with named elements. Option 2 could be in conflict with named parameters, true, but named parameters could also have a different syntax, i.e. foo(a = 3, b = 4), this is what Scala is using. -- /Jacob Carlborg
Jul 23 2018
parent rikki cattermole <rikki cattermole.co.nz> writes:
On 24/07/2018 7:11 AM, Jacob Carlborg wrote:
 On 2018-07-23 18:26, Seb wrote:
 tl;dr: the currently proposed syntax options are:


 ---
 struct S
 {
      int a = 2, b = 4, c = 6;
 }
 void foo()
 {
      bar(S({c: 10})); // Option 1
      bar(S(c: 10));   // Option 2
      bar(S{c: 10});   // Option 3
 }
 ---

 So the struct-initialization DIP has been stalled for too long and I 
 think it's time we finally get this story done.
 I personally prefer option 2, but this might be in conflict to named 
 arguments which we hopefully see in the near future too. Hence, I'm 
 leaning forward to proposing Option 1 as the recommended Option for 
 the DIP (that's also what the PoC DMD PR implements). What's your take 
 on this?

 DIP: https://github.com/dlang/DIPs/pull/71
 Rendered view: 
 https://github.com/wilzbach/DIPs/blob/struct-initialization/DIPs/DIP1xxx-sw.md 
Talking about future potential features, Option 1 could be in conflict with a tuple with named elements. Option 2 could be in conflict with named parameters, true, but named parameters could also have a different syntax, i.e. foo(a = 3, b = 4), this is what Scala is using.
We ugh can't use that syntax because of ambiguity to AssignExpression.
Jul 23 2018
prev sibling next sibling parent reply Dukc <ajieskola gmail.com> writes:
On Monday, 23 July 2018 at 16:26:42 UTC, Seb wrote:
 What's your take on this?
Option 2 won't necessarily cause problems with named funcion arguments: The names of the constructor arguments and members are different anyway, at least usually, letting the compiler to infer the intended call by them. But there might be some corner cases where this would not apply. Do you see any?
Jul 24 2018
parent reply Cym13 <cpicard openmailbox.org> writes:
On Tuesday, 24 July 2018 at 10:48:40 UTC, Dukc wrote:
 On Monday, 23 July 2018 at 16:26:42 UTC, Seb wrote:
 What's your take on this?
Option 2 won't necessarily cause problems with named funcion arguments: The names of the constructor arguments and members are different anyway, at least usually, letting the compiler to infer the intended call by them. But there might be some corner cases where this would not apply. Do you see any?
That argument sounds quite dangerous to me, especially since my experience is on the contrary that constructor arguments are often named the same as the attribute they refer to. And what of mixed cases? I really wouldn't rely on anything like naming conventions for something like that.
Jul 24 2018
parent reply Dukc <ajieskola gmail.com> writes:
On Tuesday, 24 July 2018 at 12:37:21 UTC, Cym13 wrote:
 That argument sounds quite dangerous to me, especially since my 
 experience is on the contrary that constructor arguments are 
 often named the same as the attribute they refer to. And what 
 of mixed cases? I really wouldn't rely on anything like naming 
 conventions for something like that.
I was going to ask that how can they be named the same since the argument would then shadow the member, but then I realized that this works: struct S { int a; int b; this(int a, int b) { this.a = a; this.b = b; } } Yes, you are right.
Jul 24 2018
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Jul 24, 2018 at 01:13:16PM +0000, Dukc via Digitalmars-d wrote:
 On Tuesday, 24 July 2018 at 12:37:21 UTC, Cym13 wrote:
 That argument sounds quite dangerous to me, especially since my
 experience is on the contrary that constructor arguments are often
 named the same as the attribute they refer to. And what of mixed
 cases? I really wouldn't rely on anything like naming conventions
 for something like that.
I was going to ask that how can they be named the same since the argument would then shadow the member, but then I realized that this works: struct S { int a; int b; this(int a, int b) { this.a = a; this.b = b; } } Yes, you are right.
It works, but TBH it's quite a bad idea, and very confusing to read. And TBH, if all the ctor is doing is copying its arguments to member variables, then we really should be more DRY and have special syntax for doing that, ala C++ (though the C++ syntax itself is pretty pathological... D could use better syntax, but the idea remains: get rid of redundancy like `this.a = a` or `a = _a`). T -- Старый друг лучше новых двух.
Jul 24 2018
parent Jim Balter <Jim Balter.name> writes:
On Tuesday, 24 July 2018 at 16:35:32 UTC, H. S. Teoh wrote:
 On Tue, Jul 24, 2018 at 01:13:16PM +0000, Dukc via 
 Digitalmars-d wrote:
 On Tuesday, 24 July 2018 at 12:37:21 UTC, Cym13 wrote:
 That argument sounds quite dangerous to me, especially since 
 my experience is on the contrary that constructor arguments 
 are often named the same as the attribute they refer to. And 
 what of mixed cases? I really wouldn't rely on anything like 
 naming conventions for something like that.
I was going to ask that how can they be named the same since the argument would then shadow the member, but then I realized that this works: struct S { int a; int b; this(int a, int b) { this.a = a; this.b = b; } } Yes, you are right.
It works, but TBH it's quite a bad idea, and very confusing to read.
This is a very common programming style and is found in numerous programming books and tutorials. Personal judgments as to whether it's a good idea or confusing are completely beside the point; designers of struct initialization syntax should not impose such judgments on the rest of the world, possibly forcing people to change their code and their texts.
 And TBH, if all the ctor is doing is copying its arguments to 
 member variables, then we really should be more DRY and have 
 special syntax for doing that, ala C++ (though the C++ syntax 
 itself is pretty pathological... D could use better syntax, but 
 the idea remains: get rid of redundancy like `this.a = a` or `a 
 = _a`).


 T
This too is completely off topic. And there are hundreds of thousands of extant lines of such code in various languages other than C++ (or Scala, which has a different and more concise way to avoid this boilerplate), and it hasn't been a big deal. Some people use IDE forms/macros to fill in these common lines. like a function or ctor call but isn't, and it looks like {...} conjunction with and dependent on named parameters. If named were a call to an implicit ctor that takes optional named parameters corresponding to each member, which would provide uniformity, but I think it's a bit dangerous and confusing, using the same syntax to do two different things, initialization and consistent with existing struct initialization ... does it have any downsides?
Jul 25 2018
prev sibling parent reply Neia Neutuladh <neia ikeran.org> writes:
On Monday, 23 July 2018 at 16:26:42 UTC, Seb wrote:
 I personally prefer option 2, but this might be in conflict to 
 named arguments which we hopefully see in the near future too. 
 Hence, I'm leaning forward to proposing Option 1 as the 
 recommended Option for the DIP (that's also what the PoC DMD PR 
 implements). What's your take on this?
If we have named arguments that can be reordered and, when they have default values, omitted, we don't really need a special struct initialization syntax. We just need the compiler to generate the implicit struct constructor in the obvious way, like: struct S { string a = "field a!"; int b = 10; // compiler-generated this(<string a = "field a!", int b = 10>) {...} } writeln(S(a: "hello", b: 15)); Similarly, struct initializer syntax everywhere slightly reduces the need for named arguments, albeit with some inconvenience: // named args style void drawRect(<int x, int y, int width, int height, string color>) {} // struct style struct DrawRect { int x, y, width, height; string color; } void drawRect(DrawRect rect) {}
Jul 24 2018
parent rikki cattermole <rikki cattermole.co.nz> writes:
On 25/07/2018 5:55 AM, Neia Neutuladh wrote:
 
 Similarly, struct initializer syntax everywhere slightly reduces the 
 need for named arguments, albeit with some inconvenience:
It actually doesn't surprisingly. As long as you support it for templates as well. At which point it creates a nice consistent experience.
Jul 24 2018