{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/347","labels_url":"https://api.github.com/repos/dotnet/roslyn/issues/347/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/roslyn/issues/347/comments","events_url":"https://api.github.com/repos/dotnet/roslyn/issues/347/events","html_url":"https://github.com/dotnet/roslyn/issues/347","id":57115642,"number":347,"title":"Proposal: Language support for Tuples","user":{"login":"MadsTorgersen","id":6677272,"avatar_url":"https://avatars.githubusercontent.com/u/6677272?v=3","gravatar_id":"","url":"https://api.github.com/users/MadsTorgersen","html_url":"https://github.com/MadsTorgersen","followers_url":"https://api.github.com/users/MadsTorgersen/followers","following_url":"https://api.github.com/users/MadsTorgersen/following{/other_user}","gists_url":"https://api.github.com/users/MadsTorgersen/gists{/gist_id}","starred_url":"https://api.github.com/users/MadsTorgersen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MadsTorgersen/subscriptions","organizations_url":"https://api.github.com/users/MadsTorgersen/orgs","repos_url":"https://api.github.com/users/MadsTorgersen/repos","events_url":"https://api.github.com/users/MadsTorgersen/events{/privacy}","received_events_url":"https://api.github.com/users/MadsTorgersen/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/dotnet/roslyn/labels/1%20-%20Planning","name":"1 - Planning","color":"cccccc"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Area-Language%20Design","name":"Area-Language Design","color":"5319e7"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Feature%20Request","name":"Feature Request","color":"fc2929"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Language-C%23","name":"Language-C#","color":"207de5"}],"state":"open","locked":false,"assignee":{"login":"MadsTorgersen","id":6677272,"avatar_url":"https://avatars.githubusercontent.com/u/6677272?v=3","gravatar_id":"","url":"https://api.github.com/users/MadsTorgersen","html_url":"https://github.com/MadsTorgersen","followers_url":"https://api.github.com/users/MadsTorgersen/followers","following_url":"https://api.github.com/users/MadsTorgersen/following{/other_user}","gists_url":"https://api.github.com/users/MadsTorgersen/gists{/gist_id}","starred_url":"https://api.github.com/users/MadsTorgersen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MadsTorgersen/subscriptions","organizations_url":"https://api.github.com/users/MadsTorgersen/orgs","repos_url":"https://api.github.com/users/MadsTorgersen/repos","events_url":"https://api.github.com/users/MadsTorgersen/events{/privacy}","received_events_url":"https://api.github.com/users/MadsTorgersen/received_events","type":"User","site_admin":false},"milestone":{"url":"https://api.github.com/repos/dotnet/roslyn/milestones/8","html_url":"https://github.com/dotnet/roslyn/milestones/C%23%207%20and%20VB%2015","labels_url":"https://api.github.com/repos/dotnet/roslyn/milestones/8/labels","id":962632,"number":8,"title":"C# 7 and VB 15","description":"\r\n\r\n<!---\r\n@huboard:{\"order\":0.375}\r\n-->\r\n","creator":{"login":"gafter","id":3804346,"avatar_url":"https://avatars.githubusercontent.com/u/3804346?v=3","gravatar_id":"","url":"https://api.github.com/users/gafter","html_url":"https://github.com/gafter","followers_url":"https://api.github.com/users/gafter/followers","following_url":"https://api.github.com/users/gafter/following{/other_user}","gists_url":"https://api.github.com/users/gafter/gists{/gist_id}","starred_url":"https://api.github.com/users/gafter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gafter/subscriptions","organizations_url":"https://api.github.com/users/gafter/orgs","repos_url":"https://api.github.com/users/gafter/repos","events_url":"https://api.github.com/users/gafter/events{/privacy}","received_events_url":"https://api.github.com/users/gafter/received_events","type":"User","site_admin":false},"open_issues":40,"closed_issues":2,"state":"open","created_at":"2015-02-02T23:41:34Z","updated_at":"2015-06-12T04:26:27Z","due_on":null,"closed_at":null},"comments":75,"created_at":"2015-02-10T01:06:17Z","updated_at":"2015-06-15T06:13:44Z","closed_at":null,"body":"There are many scenarios where you'd like to group a set of typed values temporarily, without the grouping itself warranting a \"concept\" or type name of its own.\r\n\r\nOther languages use variations over the notion of **tuples** for this. Maybe C# should too.\r\n\r\nThis proposal follows up on #98 and addresses #102 and #307.\r\n\r\n\r\nBackground\r\n==========\r\n\r\nThe most common situation where values need to be temporarily grouped, a list of arguments to (e.g.) a method, has syntactic support in C#. However, the probably *second*-most common, a list of *results*, does not.\r\n\r\nWhile there are many situations where tuple support could be useful, the most prevalent by far is the ability to return multiple values from an operation.\r\n\r\nYour options today include:\r\n\r\n*Out parameters:*\r\n\r\n``` c#\r\n\r\npublic void Tally(IEnumerable<int> values, out int sum, out int count) { ... }\r\n\r\nint s, c;\r\nTally(myValues, out s, out c);\r\nConsole.WriteLine($\"Sum: {s}, count: {c}\");  \r\n```\r\n\r\nThis approach cannot be used for async methods, and it is also rather painful to consume, requiring variables to be first declared (and `var` is not an option), then passed as out parameters in a separate statement, *then* consumed.\r\n\r\nOn the bright side, because the results are out parameters, they have names, which help indicate which is which.\r\n\r\n*System.Tuple:*\r\n\r\n``` c#\r\npublic Tuple<int, int> Tally(IEnumerable<int> values) { ... }\r\n\r\nvar t = Tally(myValues);\r\nConsole.WriteLine($\"Sum: {t.Item1}, count: {t.Item2}\");  \r\n```\r\n\r\nThis works for async methods (you could return `Task<Tuple<int, int>>`), and you only need two statements to consume it. On the downside, the consuming code is perfectly obscure - there is nothing to indicate that you are talking about a sum and a count. Finally, there's a cost to allocating the Tuple object.\r\n\r\n*Declared transport type*\r\n\r\n``` c#\r\npublic struct TallyResult { public int Sum; public int Count; }\r\npublic TallyResult Tally(IEnumerable<int> values) { ... }\r\n\r\nvar t = Tally(myValues);\r\nConsole.WriteLine($\"Sum: {t.Sum}, count: {t.Count}\");  \r\n```\r\n\r\nThis has by far the best consumption experience. It works for async methods, the resulting struct has meaningful field names, and being a struct, it doesn't require heap allocation - it is essentially passed on the stack in the same way that the argument list to a method.\r\n\r\nThe downside of course is the need to declare the transport type. THe declaration is meaningless overhead in itself, and since it doesn't represent a clear concept, it is hard to give it a meaningful name. You can name it after the operation that returns it (like I did above), but then you cannot reuse it for other operations.\r\n\r\nTuple syntax\r\n============\r\n\r\nIf the most common use case is multiple results, it seems reasonable to strive for symmetry with parameter lists and argument lists. If you can squint and see \"things going in\" and \"things coming out\" as two sides of the same coin, then that seems to be a good sign that the feature is well integrated into the existing language, and may in fact *improve* the symmetry instead of (or at least in addition to) adding conceptual weight.\r\n\r\nTuple types\r\n-----------\r\n\r\nTuple types would be introduced with syntax very similar to a parameter list:\r\n\r\n``` c#\r\npublic (int sum, int count) Tally(IEnumerable<int> values) { ... }\r\n\r\nvar t = Tally(myValues);\r\nConsole.WriteLine($\"Sum: {t.sum}, count: {t.count}\");  \r\n```\r\n\r\nThe syntax `(int sum, int count)` indicates an anonymous struct type with public fields of the given names and types.\r\n\r\nNote that this is different from some notions of tuple, where the members are not given names but only positions. This is a common complaint, though, essentially degrading the consumption scenario to that of `System.Tuple` above. For full usefulness, tuples members need to have names.\r\n\r\nThis is fully compatible with async:\r\n\r\n``` c#\r\npublic async Task<(int sum, int count)> TallyAsync(IEnumerable<int> values) { ... }\r\n\r\nvar t = await TallyAsync(myValues);\r\nConsole.WriteLine($\"Sum: {t.sum}, count: {t.count}\");  \r\n```\r\n\r\nTuple literals\r\n--------------\r\n\r\nWith no further syntax additions to C#, tuple values could be created as\r\n``` c#\r\nvar t = new (int sum, int count) { sum = 0, count = 0 };\r\n```\r\n\r\nOf course that's not very convenient. We should have a syntax for tuple literals, and given the principle above it should closely mirror that of argument lists.\r\n\r\nCreating a tuple value of a known target type, should enable leaving out the member names:\r\n\r\n``` c#\r\npublic (int sum, int count) Tally(IEnumerable<int> values) \r\n{\r\n    var s = 0; var c = 0;\r\n    foreach (var value in values) { s += value; c++; }\r\n    return (s, c); // target typed to (int sum, int count)\r\n}\r\n```\r\n\r\nUsing named arguments as a syntax analogy it may also be possible to give the names of the tuple fields directly in the literal:\r\n\r\n``` c#\r\npublic (int sum, int count) Tally(IEnumerable<int> values) \r\n{\r\n    var res = (sum: 0, count: 0); // infer tuple type from names and values\r\n    foreach (var value in values) { res.sum += value; res.count++; }\r\n    return res;\r\n}\r\n```\r\n\r\nWhich syntax you use would depend on whether the context provides a target type.\r\n\r\nTuple deconstruction\r\n--------------------\r\n\r\nSince the grouping represented by tuples is most often \"accidental\", the consumer of a tuple is likely not to want to even think of the tuple as a \"thing\". Instead they want to immediately get at the components of it. Just like you don't *first* bundle up the arguments to a method into an object and *then* send the bundle off, you wouldn't want to *first* receive a bundle of values back from a call and *then* pick out the pieces.\r\n\r\nLanguages with tuple features typically use a deconstruction syntax to receive and \"split out\" a tuple in one fell swoop:\r\n\r\n``` c#\r\n(var sum, var count) = Tally(myValues); // deconstruct result\r\nConsole.WriteLine($\"Sum: {sum}, count: {count}\");  \r\n```\r\n\r\nThis way there's no evidence in the code that a tuple ever *existed*. \r\n\r\n\r\nDetails\r\n=======\r\n\r\nThat's the general gist of the proposal. Here are a ton of details to think through in the design process.\r\n\r\nStruct or class\r\n---------------\r\n\r\nAs mentioned, I propose to make tuple types structs rather than classes, so that no allocation penalty is associated with them. They should be as lightweight as possible.\r\n\r\nArguably, structs can end up being more costly, because assignment copies a bigger value. So if they are assigned a lot more than they are created, then structs would be a bad choice.\r\n\r\nIn their very motivation, though, tuples are ephemeral. You would use them when the parts are more important than the whole. So the common pattern would be to construct, return and immediately deconstruct them. In this situation structs are clearly preferable.\r\n\r\nStructs also have a number of other benefits, which will become obvious in the following.\r\n\r\nMutability\r\n----------\r\n\r\nShould tuples be mutable or immutable? The nice thing about them being structs is that the user can choose. If a reference to the tuple is readonly then the tuple is readonly. \r\n\r\nNow a local variable cannot be readonly, unless we adopt #115 (which is likely), but that isn't too big of a deal, because locals are only *used* locally, and so it is easier to stick to an immutable discipline if you so choose.\r\n\r\nIf tuples are used as fields, then those fields can be readonly if desired.\r\n\r\nValue semantics\r\n---------------\r\n\r\nStructs have built-in value semantics: `Equals` and `GetHashCode` are  automatically implemented in terms of the struct's fields. This isn't always very *efficiently* implemented, so we should make sure that the compiler-generated struct does this efficiently where the runtime doesn't.\r\n\r\nTuples as fields\r\n----------------\r\n\r\nWhile multiple results may be the most common usage, you can certainly imagine tuples showing up as part of the state of objects. A particular common case might be where generics is involved, and you want to pass a compound of values for one of the type parameters. Think dictionaries with multiple keys and/or multiple values, etc.\r\n\r\nCare needs to be taken with mutable structs in the heap: if multiple threads can mutate, tearing can happen.\r\n\r\nConversions\r\n-----------\r\n\r\nOn top of the member-wise conversions implied by target typing, we can certainly allow implicit conversions between tuple types themselves.\r\n\r\nSpecifically, covariance seems straightforward, because the tuples are value types: As long as each member of the assigned tuple is assignable to the type of the corresponding member of the receiving tuple, things should be good.\r\n\r\nYou could imagine going a step further, and allowing pointwise conversions between tuples *regardless* of the member names, as long as the arity and types line up. If you want to \"reinterpret\" a tuple, why shouldn't you be allowed to? Essentially the view would be that assignment from tuple to tuple is just memberwise assignment by position.\r\n\r\n``` c#\r\n(double sum, long count) weaken = Tally(...); // why not?\r\n(int s, int c) rename = Tally(...) // why not?\r\n```\r\n\r\nUnification across assemblies\r\n-----------------------------\r\n\r\nOne big question is whether tuple types should unify across assemblies. Currently, compiler generated types don't. As a matter of fact, anonymous types are deliberately kept assembly-local by limitations in the language, such as the fact that there's no type syntax for them!\r\n\r\nIt might seem obvious that there should be unification of tuple types across assemblies - i.e. that `(int sum, int count)` is the same type when it occurs in assembly A and assembly B. However, given that structs aren't expected to be passed around much, you can certainly imagine them still being useful without that.\r\n\r\nEven so, it would probably come as a surprise to developers if there was no interoperability between tuples across assembly boundaries. This may range from having implicit conversions between them, supported by the compiler, to having a true unification supported by the runtime, or implemented with very clever tricks. Such tricks might lead to a less straightforward layout in metadata (such as carrying the tuple member names in separate attributes instead of as actual member names on the generated struct).\r\n\r\nThis needs further investigation. What would it take to implement tuple unification? Is it worth the price? Are tuples worth doing without it?\r\n\r\nDeconstruction and declaration\r\n------------------------------\r\n\r\nThere's a design issue around whether deconstruction syntax is only for declaring *new* variables for tuple components, or whether it can be used with existing variables:\r\n\r\n``` c#\r\n(var sum, var count) = Tally(myValues); // deconstruct into fresh variables\r\n(sum, count) = Tally(otherValues); // deconstruct into existing variables?\r\n```\r\n\r\nIn other words is the form `(_, _, _) = e;` a declaration statement, an assignment expression, or something in between?\r\n\r\nThis discussion intersects meaningfully with #254, declaration expressions.\r\n\r\nRelationship with anonymous types\r\n---------------------------------\r\n\r\nSince tuples would be compiler generated types just like anonymous types are today, it's useful to consider rationalizing the two with each other as much as possible. With tuples being structs and anonymous types being classes, they won't completely unify, but they could be very similar. Specifically, anonymous types could pick up these properties from tuples:\r\n\r\n- There could be a syntax to denote the types! E.g. `{ string Name, int Age}`. If so, we'd need to also figure out the cross-assembly story for them.\r\n- There could be deconstruction syntax for them.\r\n\r\nOptional enhancements\r\n=====================\r\n\r\nOnce in the language, there are additional conveniences that you can imagine adding for tuples.\r\n\r\nTuple members in scope in method body\r\n-------------------------------------\r\n\r\nOne (the only?) nice aspect of out parameters is that no returning is needed from the method body - they are just assigned to. For the case where a tuple type occurs as a return type of a method you could imagine a similar shortcut:\r\n\r\n``` c#\r\npublic (int sum, int count) Tally(IEnumerable<int> values) \r\n{\r\n    sum = 0; count = 0;\r\n    foreach (var value in values) { sum += value; count++; }\r\n}\r\n```\r\n\r\nJust like parameters, the names of the tuple are in scope in the method body, and just like out parameters, the only requirement is that they be definitely assigned at the end of the method.\r\n\r\nThis is taking the parameter-result analogy one step further. However, it would special-case the tuples-for-multiple-returns scenario over other tuple scenarios, and it would also preclude seeing in one place what gets returned. \r\n\r\nSplatting\r\n---------\r\n\r\nIf a method expects n arguments, we could allow a suitable n-tuple to be passed to it. Just like with params arrays, we would first check if there's a method that takes the tuple directly, and otherwise we would try again with the tuple's members as individual arguments:\r\n\r\n``` c#\r\npublic double Avg(int sum, int count) => count==0 ? 0 : sum/count;\r\n\r\nConsole.WriteLine($\"Avg: {Avg(Tally(myValues))}\");\r\n```\r\n\r\nHere, `Tally` returns a tuple of type `(int sum, int count)` that gets splatted to the two arguments to `Avg`.\r\n\r\nConversely, if a method expects a tuple we could allow it to be called with individual arguments, having the compiler automatically assemble them to a tuple, provided that no overload was applicable to the individual arguments.\r\n\r\nI doubt that a method would commonly be declared directly to just take a tuple. But it may be a method on a generic type that gets instantiated with a tuple type:\r\n\r\n``` c#\r\nvar list = List<(string name, int age)>();\r\nlist.Add(\"John Doe\", 66); // \"unsplatting\" to a tuple\r\n```\r\n\r\nThere are probably a lot of details to figure out with the splatting and unsplatting rules.\r\n\r\n"},"comment":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/comments/111941488","html_url":"https://github.com/dotnet/roslyn/issues/347#issuecomment-111941488","issue_url":"https://api.github.com/repos/dotnet/roslyn/issues/347","id":111941488,"user":{"login":"GeirGrusom","id":4979268,"avatar_url":"https://avatars.githubusercontent.com/u/4979268?v=3","gravatar_id":"","url":"https://api.github.com/users/GeirGrusom","html_url":"https://github.com/GeirGrusom","followers_url":"https://api.github.com/users/GeirGrusom/followers","following_url":"https://api.github.com/users/GeirGrusom/following{/other_user}","gists_url":"https://api.github.com/users/GeirGrusom/gists{/gist_id}","starred_url":"https://api.github.com/users/GeirGrusom/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/GeirGrusom/subscriptions","organizations_url":"https://api.github.com/users/GeirGrusom/orgs","repos_url":"https://api.github.com/users/GeirGrusom/repos","events_url":"https://api.github.com/users/GeirGrusom/events{/privacy}","received_events_url":"https://api.github.com/users/GeirGrusom/received_events","type":"User","site_admin":false},"created_at":"2015-06-15T06:13:44Z","updated_at":"2015-06-15T06:13:44Z","body":"@gafter more than four should be called 'alotple'."}}