Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Projection initializer for tuples #13319

Closed
alrz opened this issue Aug 23, 2016 · 13 comments
Closed

Proposal: Projection initializer for tuples #13319

alrz opened this issue Aug 23, 2016 · 13 comments
Assignees

Comments

@alrz
Copy link
Contributor

alrz commented Aug 23, 2016

Since tuples won't infer names from the expression [1], would it make sense to be able to use a property initializer with tuples to retain similar behavior?

var x =
  from item in list
  select new ValueTuple<string, int> { item.Name, item.Age }; 

would be equivalent to:

var x =
  from item in list
  select ( Name: item.Name, Age: item.Age );

Using #35 one should be able to omit the type so that new () { .. } defaults to a tuple:

var x =
  from item in list
  select new () { item.Name, item.Age }; 

Alternatively we could use a "struct anonymous type",

var x =
  from item in list
  select new struct { item.Name, item.Age };

This could generate an internal struct or just use a tuple under the hood.

[1] This was mentioned in #10429 but if anything, the original idea (using regular tuple syntax) would be a better solution in any ways.

(int, int) t = (x, y); // not named 
(int a, int b) t = (x, y); // named according to the type
var t = (x, y); // all named
var t = (x, x); // not named
var t = (n: x, x); // all named (partial explicit naming)
var t = (x, 5); // partially named
var t = (x, x, y); // ditto
@DavidArno
Copy link

I'm confused what you are asking for here, as the following works just fine:

var x =
  from item in list
  select (item.Name, item.Age);

@HaloFour
Copy link

@DavidArno

He's asking for a way to have the tuple elements automatically named based on the properties used to initialize them, as with anonymous types. Your code example does work but the tuples go unnamed.

var x =
  from item in list
  select (item.Name, item.Age);

for (var y in x) {
    Console.WriteLine($"Hello {y.Name}!");  // oops, you'd need to use y.Item1
}

I'm curious as to why the compiler couldn't just automatically use the same rules as it does with anonymous types in this context? Does it introduce any ambiguity?

@DavidArno
Copy link

DavidArno commented Aug 23, 2016

@HaloFour,

In that case, the second piece of code compiles and works just fine (using VS15 Preview 4):

var x =
  from item in list
  select (Name: item.Name, Age: item.Age);

So I'm still confused 😄

@HaloFour
Copy link

@DavidArno

That it does. That's why he states that he wants the first example to be equivalent to the second example. He doesn't want to have to explicitly provide the tuple names where they could be automatically deduced.

@DavidArno
Copy link

DavidArno commented Aug 23, 2016

@HaloFour,

Ah, got it now. Thanks for taking the time to explain. In thicky-mode today 😄

@alrz
Copy link
Contributor Author

alrz commented Aug 23, 2016

I'm curious as to why the compiler couldn't just automatically use the same rules as it does with anonymous types in this context? Does it introduce any ambiguity?

@HaloFour I don't recall the issue number (design notes) but this idea was rejected because tuple item names are not mandatory compared to anonymous types where they are and there must not be any duplication. However, since names are insignificant in type equivalency I don't really think that is a blocker.

@HaloFour
Copy link

@alrz

I haven't been able to find any design notes on the subject either. If anything I think that since the names are not mandatory that there's less reason to not implicitly name the elements. It's not like doing so would break accessing them by their underlying tuple property name.

It's my opinion that a common use case for tuples would be projections just as you demonstrated, and either having to either explicitly specify the name or reference the element by position will both be considered obnoxious (certainly not DRY). Seems like a simple win.

@DavidArno
Copy link

Now I understand this proposal (thanks @HaloFour), I'm in complete agreement with it and think it a sensible idea. For the first code example:

var x =
  from item in list
  select new ValueTuple<string, int> { item.Name, item.Age }; 

The complier is inferring the types of the tuple and can infer the names too. So it should. Calling them Name and Age, rather than Item1 and Item2 doesn't introduce problems and avoids unnecessary label repetition (thus provides better DRY support as @HaloFour points out).

It should be limited to such simple scenarios though, eg if the code were:

var x =
  from item in list
  select new ValueTuple<string, int> { item.Name, item.Age * 2 }; 

Then in this case, the second value is not Age any more, so shouldn't be labelled as such. It therefore probably follows that the first value shouldn't be labelled Name either.

@alrz
Copy link
Contributor Author

alrz commented Aug 24, 2016

@DavidArno That is nothing new, you can refer to end of the 7.6.10.6 section in C# spec. The problem is that this can be intuitively integrated to the regular tuple literals, so that (item.Name, item.Age) would be equivalent to (Name: item.Name, Age: item.Age) -- name and type both inferred from the expression. For some reason it's not. Since partial explicit naming is on the table right now, this is actually something that should be reconsidered IMO.

@alrz
Copy link
Contributor Author

alrz commented Aug 25, 2016

@HaloFour After digging through design notes I finally found the relevant part,

#10429:

Projection initializers

Tuple literals are a bit like anonymous types. The latter have "projection initializers" where if you don't specify a member name, one will be extracted from the given expression, if possible. Should we do that for tuples too?

var a = new { Name = c.FirstName, c.Age }; // Will have members Name and Age
var t = (Name: c.FirstName, c.Age); // (string Name, int Age) or error?

We don't think so. The difference is that names are optional in tuples. It'd be too easy to pick up a random name by mistake, or get errors because two elements happen to pick up the same name.

#13022:

All or nothing with element names?

There's certainly a case for partial names on literals - sometimes you want to specify the names of some of the elements for clarity. These names have to fit names in the tuple type that the literal is being converted to:

List<(string firstName, string lastName, bool paid)> people = ...;
people.Add(("John", "Doe", paid: true)); 

Do we also want to allow partial names in types?

Yes we do. We don't have strong arguments against it, and generality calls for it.

I'll note that name inference makes the bizarre ItemX members almost invisible without requiring the user to opt-in to explicit naming.

/cc @MadsTorgersen

@jcouv jcouv self-assigned this Sep 1, 2016
@aluanhaddad
Copy link

aluanhaddad commented Sep 17, 2016

This is a good idea. If there is a meaningful name that can be inferred and served up to consumers, especially given the precedent set by anonymous types, there is lot to be gained from this. The difference that anonymous types require names be specified where names cannot be inferred but that tuples work in the opposite manner is not a reason not to do this. This is a tooling win.

@alrz
Copy link
Contributor Author

alrz commented Dec 10, 2016

@jcouv Any news on this? I don't think that it would be a breaking change to add this later though.

@jcouv
Copy link
Member

jcouv commented Dec 10, 2016

@alrz I agree. I don't think this would be a breaking change to add later either. That makes it less of a priority to decide now compared to other issues we're currently working through (discards, scoping rules for out and pattern variables, tuple errors that can't be introduced later, ...).

So this won't be in C#7.0. After that, it will be good to see more real-world usage with tuples and deconstruction. This experience and feedback will help prioritize and trade-off features.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants