DIPs icon indicating copy to clipboard operation
DIPs copied to clipboard

[Revived] In-place struct initialization

Open wilzbach opened this issue 7 years ago • 36 comments

This is a revival of #22, because I thought the idea is cool and I often could profit from the changes of this DIP.

I am not an expert on the D grammar, so @ everyone, please feel to criticize the proposed additions.

CC @cym13 @Enamex

As this is an adopted PR, help to polish it is more than welcome.

wilzbach avatar Jun 08 '17 02:06 wilzbach

I think the focus should be on the inconsistency in the language, that is, that there's this very specific syntax { foo: 3 } that only works in one particular case. Rather than focusing on named arguments.

jacob-carlborg avatar Jun 11 '17 13:06 jacob-carlborg

My experience writing https://github.com/dlang/DIPs/pull/66 gave me some insight into how a persuasive DIP runs in the general case. I'm copying this comment I made for https://github.com/dlang/DIPs/pull/61 , as it applies here too. My preferred order for DIP presentation is:

Section: Rationale (i.e. State the Problem) What you want to do/be able to do, and why. What you currently must do to achieve the effect in question. Code example. Why a better solution is desirable. Who is the "customer" for the better way?

Section: Description Now describe your proposed feature.

Section: Drawbacks and Alternatives (drawbacks, i.e. breakage, language complexity, corner cases) Be as honest as possible. Try to argue the opposition's case for them. Imagine you're a language designer trying to find reasons not to include yet another feature. List all known viable alternatives.

zachthemystic avatar Jun 13 '17 03:06 zachthemystic

I think the grammar descriptions and AST sections are particularly unpersuasive. The grammar should be moved to the bottom of the DIP, and only kept because it's necessary for a complete proposal. But I think the AST sections can be completely removed. I really can't imagine anyone being persuaded by them. If there are any parsing problems, they should be addressed somewhere else.

zachthemystic avatar Jun 13 '17 04:06 zachthemystic

@jacob-carlborg @ntrel @zachthemystic thanks a lot for your helpful feedback - I am just trying my best at reviving the best, so please have a bit of patience.

@jacob-carlborg

I think the focus should be on the inconsistency in the language, that is, that there's this very specific syntax { foo: 3 } that only works in one particular case. Rather than focusing on named arguments.

Fair point - I tried to reword it, but it still needs more. Feel free to point out sections that you thought weren't helpful.

@zachthemystic: thanks a lot for the idea - I restructured the page and put the rationale after the description.

Try to argue the opposition's case for them. Imagine you're a language designer trying to find reasons not to include yet another feature. List all known viable alternatives.

Except for the workarounds with named arguments, I couldn't a reason not to allow in-place struct initialization anywhere. Ideas?

I think the grammar descriptions and AST sections are particularly unpersuasive.

Thanks - what in your opinion could I add to help in convincing a reader?

But I think the AST sections can be completely removed. I really can't imagine anyone being persuaded by them. If there are any parsing problems, they should be addressed somewhere else.

Hmm I found it very helpful to understand the consequences of the grammar changes - what do other people think on this?

wilzbach avatar Jun 13 '17 20:06 wilzbach

My first approach is as a general reader. I think the technical decisions can be very difficult, as there are many pros and cons, which must be weighed very accurately. So I'm starting by ignoring the technical details, trying to assess the quality of the writing for its persuasiveness. The following are my opinions, and I don't consider them absolutely right or wrong.

The Abstract should be punchier, starting with the 2nd sentence. For example: "This DIP proposes to expand support for static struct initialization to any place where calling a struct constructor would be possible." Imagine you only have one sentence to grab the reader's attention. If you absolutely need more sentences, then okay, but the first one needs to say concisely and in active terms what the DIP does.

The Links section is okay, not because it grabs, but simply because it's necessary.

So then I get to Description, and I'm already kind of losing interest. I need to be convinced that there's a problem. Imagine the reader has a thousand other things to do than read the DIP (which is probably true). What can you say to convince them there is an important problem here that is worth their attention? That is why I would rather read the rationale at this point. Only when I'm convinced there's a problem do I need to see the solution.

The existing Rationale which follows does not actually do this, in the sense that it promotes benefits without making the problem felt.

I realize that none of what I'm saying actually addresses technical concerns. But I have enough confidence that whoever reads this, including the language authors, are human beings, who are always affected by many things other than pure logic, which is why I'm saying it.

I'll continue to review if and when the above comments are addressed.

zachthemystic avatar Jun 13 '17 21:06 zachthemystic

the work on this dip is highly appreciated. For my AWS SDK this DIP would make the coding much more readable and also smaller for several use cases.

I generate structures out of the AWS API information. Several UDA information has to be stored. Struct initializer for UDA structures will look great:

struct CreateTableInput
{
	@FieldInfo({memberName: "TableName"})
	TableName tableName;

	@FieldInfo({memberName: "AttributeDefinitions", minLength: 1})
	AttributeDefinitions attributeDefinitions;
}

Second scenario is the actual usage of these structs. Using struct initializer in method signature feels natural:

invoker.execute([
	new CreateBucketCommand(client, {
		bucket: "MyBucket1",
		createBucketConfiguration: {
			locationConstraint: BucketLocationConstraint.EU_CENTRAL_1
		}
	}),
	new CreateBucketCommand(client, {
		bucket: "MyBucket2",
		createBucketConfiguration: {
			locationConstraint: BucketLocationConstraint.EU_CENTRAL_1
		}
	})
]);

andre2007 avatar Jun 14 '17 20:06 andre2007

@wilzbach I'm going to agree with @jacob-carlborg about focus. As it reads now, it sounds more like a DIP for named arguments than for struct initialization. IMO, consistency is the key selling point here, as is the principle of least surprise; if it works in declarations, it's reasonable to expect that it should work elsewhere and surprising that it doesn't. And if memory serves, Walter is against named arguments (there have been multiple discussions in the forums), so repeating the phrase so frequently may actually be counter-productive!

mdparker avatar Jun 18 '17 17:06 mdparker

It's a nice DIP IMO. I sometimes have the case of defavoring struct constructor syntax in favor of field by field assignment for the sake of readability.

Consistency with the static assignment syntax would be to accept this very syntax everywhere a struct can be initialized:

struct S {
    uint a;
    long b;
    int  c;
}
struct SS {
    uint d;
    S s;
}
void foo (int e, S s);
void bar (SS ss, int f);

S s = { a: 42, b: -5 };  // c initialized as int.init
SS ss = { s: { a: 42, b: -5 } };

foo(12, { a: 42, b: -5 });
bar({ s: { a: 42, b: -5 } }, 54);

// without forgetting default arguments
// following a and b known at compile time, previous ones can be evaluated at run time
void baz (int g, S s = { a: 42, b: -5 });

How about the case of a template argument? It should be possible to optionally specify the type:

void tfoo(T)(int e, T s);
tfoo(12, S { a: 42, b: -5 });

The S { ... } syntax is kind of loosing consistency with the regular constructor syntax, but impossible to infer the types in template instantiation context otherwise, and a regular S ( ... ) syntax would open the door to the persona non grata named arguments.

In that case, 2 options:

  • type name mandatory for every struct named field initialization
  • type name optional but allowed everywhere (mandatory only for template arguments)

I prefer the latter:

  • not breaking currently allowed syntax
  • S is not verbose, but TemplateStruct!(Type1, "value 2") is.

rtbo avatar Jun 25 '17 13:06 rtbo

Here's an idea to get around having either named argument-syntax for this purpose or something that conflicts with q{} strings:

There was an old proposal that I faintly remember about having {arg0, arg1, ...} syntax for tuples. The idea is: Something like this for tuples with named arguments.

So we could then have:

auto my_anon_struct = {foo: 42, bar: 89}; // type == Tuple!(int, "foo", int, "bar")
                                          // or something like it
auto my_var = {foo: 42, bar: 89}.as!MyStruct; // type == MyStruct

pragma(inline, true)
auto as(T, U)(auto ref U u) {
    T t = mixin(getTupleLiteral!(U, "u"));
    // This gives us something like:
    // T t = {foo: u.foo, bar: u.bar};
    // Pretty straightforward so hopefully the compiler can
    // reliably inline
    return t;
}

munael avatar Jun 28 '17 09:06 munael

Might be of interest in relation to this 'anonymous records as field-named tuples' suggestion:

https://www.microsoft.com/en-us/research/wp-content/uploads/1999/01/recpro.pdf (this paper talks in the context of Haskell; ignoring the extensibility aspect, I think it's relevant and interesting).

I don't mean to sideline the suggestions laid out in this proposal. This's merely the only place I know of right now that has an ongoing discussion about it and I'm not sure about starting an NG thread for this while a proposal is alive.

munael avatar Jul 12 '17 23:07 munael

Found this thread on the NG: http://forum.dlang.org/post/[email protected]

@ others thanks a lot for your feedback. Highly appreciated, but unfortunately I won't have much time for this DIP this month, so if you want to push it feel free to open PRs against my branch.

wilzbach avatar Jul 14 '17 08:07 wilzbach

In the State of D survey, in-place struct initialization was the fourth-most missed language feature with 28% of all respondents missing it:

image

https://rawgit.com/wilzbach/state-of-d/master/report.html

wilzbach avatar Mar 15 '18 17:03 wilzbach

In the State of D survey, in-place struct initialization was the fourth-most missed language feature with 28% of all respondents missing it

So what are we waiting for 😃.

jacob-carlborg avatar Mar 15 '18 17:03 jacob-carlborg

Yeah, what are you waiting for?

FeepingCreature avatar Jun 07 '18 06:06 FeepingCreature

I think the main blocker is the lack of consensus on a syntax. Not sure how to go about it, people can just shout what they think is the best idea but it won't amount to much, and bringing that issue to the forums would be... let's call that a risky bet. Some kind of structured poll maybe?

cym13 avatar Jun 07 '18 07:06 cym13

So what are we waiting for smiley. Yeah, what are you waiting for?

Well I was quite busy and somehow no one else pushed this further :/ Also I wanted to implement this in DMD to show that it's absolutely possible to do, know that my proposed grammar changes are actually correct and increase the rate of acceptance of this DIP. As I got this working in DMD (it's still pretty ugly, so the PR will follow later) I'm feeling more confident to finally start submitting this PR to DIP queue. I made an entire overhaul of this DIP, so any feedback on this reworked version is very welcome!

I think the main blocker is the lack of consensus on a syntax.

AFAICT

  • Option 3 (Foo{a: 1} would only be possible in a more restricted way and isn't well-liked anyhow
  • Option 2 - that's potentially conflicting with named arguments (see e.g. https://github.com/dlang/DIPs/pull/126). I think it could be done without conflicts, but I'm not sure which named arguments DIP will be accepted.

So chances are that we will end up with Option 1, but I expected this issue to be discussed in-depth in the first review stage of this DIP.

wilzbach avatar Jul 02 '18 07:07 wilzbach

Option 1 could just as well be in conflict with tuples with names.

jacob-carlborg avatar Jul 02 '18 08:07 jacob-carlborg

I like that revived proposal manly focuses on the current inconsistencies/limitations rather than named arguments. :+1:

jacob-carlborg avatar Jul 02 '18 08:07 jacob-carlborg

@wilzbach Option 2 will definitely conflict with named arguments, given my DIP and others like 66.

But there is another problem with 2.

void foo(Bar a, Bar b);
foo(a:1, b:2, a:1:, b:3);

The arguments should be grouped anyway, otherwise it will introduce restrictions which are quite artificial. But fundamentally what I'm seeing is named parameters as API which is what my DIP is trying to achieve. With an additional edge case.

However the problem I am seeing with this DIP currently is ambiguity with templates aka what happens when type deduction fails; I do expect that to be asked about on D.learn quite a bit, especially early on.

I think more experimentation on the declaration side might be a good way to go. After all, the goal is to change what .init is for a type from the outside of it, in select locations.

rikkimax avatar Jul 03 '18 10:07 rikkimax

BTW, C++11 supports this, using the following syntax:

struct Foo
{
    int a;
    int b;
};

auto a = Foo{ .b = 4 };

jacob-carlborg avatar Jul 04 '18 14:07 jacob-carlborg

As I got this working in DMD (it's still pretty ugly, so the PR will follow later)

-> https://github.com/dlang/dmd/pull/8460 It's still ugly and just a PoC, but I hope it allows to understand this proposal better

However the problem I am seeing with this DIP currently is ambiguity with templates aka what happens when type deduction fails; I do expect that to be asked about on D.learn quite a bit, especially early on.

Does this also apply to Option 1? (the default option of this DIP and the option used for the PoC DMD PR).

@mdparker is there a slot in the DIP queue available at the moment and what do you think still needs to be done for an initial submission?

wilzbach avatar Jul 06 '18 05:07 wilzbach

@wilzbach looks like my analysis was incorrect. This statement was less than adequately explained in examples I think "In-place struct initialization behaves analogous to default constructor of structs and thus is only allowed when there's no user-defined constructor.". So type deduction shouldn't be an issue after all. But a concern would be, are we calling a constructor or doing initialization?

Of course my immediate assumption that 1 and 2 would have some sort of function calling behavior applied to it, is concerning.

rikkimax avatar Jul 06 '18 06:07 rikkimax

But a concern would be, are we calling a constructor or doing initialization?

Initialization. FWIW with dlang/dmd#8460 foo and foo2 are equivalent and do generate the same assembly:

struct S
{
    int a = 2, b = 4, c = 6;
}
void foo()
{
    bar(S({c: 10}));
}
void foo2()
{
    S s = {c: 10};
    bar(s);
}
void bar(S);
0000000000000000 <_D3fooQeFZv>:
   0:	55                   	push   %rbp
   1:	48 8b ec             	mov    %rsp,%rbp
   4:	48 83 ec 10          	sub    $0x10,%rsp
   8:	c7 45 f0 02 00 00 00 	movl   $0x2,-0x10(%rbp)
   f:	c7 45 f4 04 00 00 00 	movl   $0x4,-0xc(%rbp)
  16:	c7 45 f8 0a 00 00 00 	movl   $0xa,-0x8(%rbp)
  1d:	48 8b 55 f8          	mov    -0x8(%rbp),%rdx
  21:	48 8b 7d f0          	mov    -0x10(%rbp),%rdi
  25:	48 89 d6             	mov    %rdx,%rsi
  28:	e8 00 00 00 00       	callq  2d <_D3fooQeFZv+0x2d>
  2d:	c9                   	leaveq 
  2e:	c3                   	retq   

wilzbach avatar Jul 06 '18 06:07 wilzbach

Regarding the third option is ambiguous with token strings. We already have the problem with attributes and UDAs. That is, I can declare a struct with the name property but I cannot use it as a UDA because that conflicts with the built-in attribute. I don't have a problem using the third option.

jacob-carlborg avatar Jul 06 '18 06:07 jacob-carlborg

One question: since we're talking about initialization and not construction, should the following be legal (assuming syntax option 1)?

struct S {
    int a;
    this(int i) { this.a = i; }
}

S mystruct = S({a: 12})(13); // Calls the int constructor of the struct of type S initialized with a=12
assert(mystruct.a == 13);

If not, how should that be justified? If so, should there be any form of restriction or something?

cym13 avatar Jul 23 '18 17:07 cym13

One question: since we're talking about initialization and not construction, should the following be legal (assuming syntax option 1)?

@cym13 No. Since if you define a constructor the initializer syntax cannot be used:

struct Foo
{
    int a;
    this(int a) {}
}

void main()
{
    Foo f = { a: 3 };
}
Error: struct `Foo` has constructors, cannot use `{ initializers }`, use `Foo( initializers )` instead

jacob-carlborg avatar Jul 23 '18 18:07 jacob-carlborg

For reference the most recent discussion on the forum about this DIP was here: https://forum.dlang.org/post/[email protected]

wilzbach avatar Aug 21 '18 08:08 wilzbach

#126 does not conflict with syntax option 2, as I see it. The reason is that that #126 states that named parameters do not affect overloading resolution. This means that a constructor call where all parameters are named has same overloading as a constructor without parameters -and defining one for structs is currently forbidden. No conflict.

If this is extended to classes, there will be conflict with default constructors, but I don't see it as a problem because it won't break anything. Just disallow in-place initialization for classes that have an explicit default constructor.

dukc avatar Sep 12 '18 09:09 dukc

In all 3 options, the name of the struct is mentioned. I wonder wheter in addition it would be possible leave out the struct name in case the compiler could detect there is only 1 distinct structure which fits. For example a function "foo" only accepts structure "A" it would be possible to just write foo({bar: ...}}; Or to write it with the structure name A. foo(A{bar: ...}};

andre2007 avatar Mar 31 '19 16:03 andre2007

I just saw, it is already described in "bonus 1" :)

andre2007 avatar Apr 01 '19 17:04 andre2007