{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/11031","repository_url":"https://api.github.com/repos/dotnet/roslyn","labels_url":"https://api.github.com/repos/dotnet/roslyn/issues/11031/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/roslyn/issues/11031/comments","events_url":"https://api.github.com/repos/dotnet/roslyn/issues/11031/events","html_url":"https://github.com/dotnet/roslyn/issues/11031","id":152851718,"number":11031,"title":"C# Design Notes for Apr 12-22, 2016","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/Area-Language%20Design","name":"Area-Language Design","color":"5319e7"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Design%20Notes","name":"Design Notes","color":"fc2929"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/New%20Language%20Feature%20-%20Out%20Variable%20Declaration","name":"New Language Feature - Out Variable Declaration","color":"bfd4f2"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/New%20Language%20Feature%20-%20Pattern%20Matching","name":"New Language Feature - Pattern Matching","color":"bfd4f2"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/New%20Language%20Feature%20-%20Tuples","name":"New Language Feature - Tuples","color":"bfd4f2"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":1,"created_at":"2016-05-03T19:10:47Z","updated_at":"2016-05-04T01:04:15Z","closed_at":null,"body":"C# Design Notes for Apr 12-22, 2016\r\n===================================\r\n\r\nThese notes summarize discussions across a series of design meetings in April on several topics related to tuples and patterns:\r\n\r\n* Tuple syntax for non-tuple types\r\n* Tuple deconstruction\r\n* Tuple conversions\r\n* Deconstruction and patterns\r\n* Out vars and their scope\r\n\r\nThere's still much more to do here, but lots of progress.\r\n\r\n\r\nTuple syntax for other types\r\n============================\r\n\r\nWe are introducing a new family of `System.ValueTuple<...>` types to support tuples in C#. However, there are already types that are tuple-like, such as `System.Tuple<...>` and `KeyValuePair<...>`. Not only are these often used throughout C# code, but the former is also what's targeted by F#'s tuple mechanism, which we'd want our tuple feature to interoperate well with.\r\n\r\nAdditionally you can imagine allowing other types to benefit from some, or all, of the new tuple syntax.\r\n\r\nThe obvious kinds of interop would be tuple construction and deconstruction. Since tuple literals are target typed, consider a tuple literal that is assigned to another tuple-like type:\r\n\r\n``` c#\r\nSystem.Tuple<int, string> t = (5, null);\r\n```\r\nWe could think of this as calling `System.Tuple`'s constructor, or as a conversion, or maybe something else. Similarly, tuples will allow deconstruction and \"decorated\" names tracked by the compiler. Could we allow those on other types as well?\r\n\r\nThere are several levels of support we could decide land on:\r\n1. Only tuples are tuples. Other types are on their own.\r\n2. Specific well-known tuple-like things are also tuples (probably `Tuple<...>` and `KeyValuePair<...>`).\r\n3. An author of a type can make it tuple-like through certain API patterns\r\n4. I can make anything work with tuple syntax without being the author of it\r\n5. All types just work with it\r\n\r\nLevel 2 would be enough to give us F# interop and improve the experience with existing APIs using `Tuple<...>` and `KeyValuePair<...>`. Option 3 could rely on any kind of declarations in the type, whereas option 4 would limit that to instance-method patterns that someone else could add through extension methods. \r\n\r\nIt is hard to see how option 5 could work for deconstruction, but it might work for construction, simply by treating a tuple literal as an argument list to the type's constructor. One might consider it invasive that a type's constructor can be called without a `new` keyword or any mention of the type! On the other hand this might also be seen as a really nifty abbreviation. One problem would be how to use it with constructors with zero or one argument. So far we haven't opted to add syntax for 0-tuples and 1-tuples.\r\n\r\nWe haven't yet decided which level we want to target, except we want to at least make  `Tuple<...>` and `KeyValuePair<...>` work with tuple syntax. Whether we want to go further is a decision we probably cannot put off for a later version, since a later addition of capabilities might clash with user-defined conversions.\r\n\r\n\r\nTuple deconstruction\r\n====================\r\n\r\nWhether deconstruction works for other types or not, we at least want to do it for tuples. There are three contexts in which we consider tuple deconstruction:\r\n\r\n1. **Assignment:** Assign a tuple's element values into existing variables\r\n2. **Declaration:** Declare fresh variables and initialize them with a tuple's element values\r\n3. **Pattern matching:** Recursively apply patterns to each of a tuple's element values\r\n\r\nWe would like to add forms of all three.\r\n\r\nDeconstructing assignments\r\n--------------------------\r\n\r\nIt should be possible to assign to existing variables, to fields and properties, array elements etc., the individual values of a tuple:\r\n\r\n``` c#\r\n(x, y) = currentFrame.Crop(x, y); // x and y are existing variables\r\n(a[x], a[x+1], a[x+2]) = GetCoordinates();\r\n```\r\n\r\nWe need to be careful with the evaluation order. For instance, a swap should work just fine:\r\n\r\n``` c#\r\n(a, b) = (b, a); // Yay!\r\n```\r\n\r\nA core question is whether this is a new syntactic form, or just a variation of assignment expressions. The latter is attractive for uniformity reasons, but it does raise some questions. Normally, the type and value of an assignment expression is that of its left hand side after assignment. But in these cases, the left hand side has multiple values and types. Should we construct a tuple from those and yield that? That seems contrary to this being about *deconstructing* not *constructing* tuples!\r\n\r\nThis is something we need to ponder further. As a fallback we can say that this is a new form of assignment *statement*, which doesn't produce a value.\r\n\r\nDeconstructing declarations\r\n---------------------------\r\n\r\nIn most of the places where local variables can be introduced and initialized, we'd like to allow deconstructing declarations - where multiple variables are declared, but assigned collectively from a single tuple (or tuple-like value):\r\n\r\n``` c#\r\n(var x, var y) = GetCoordinates();             // locals in declaration statements\r\nforeach ((var x, var y) in coordinateList) ... // iteration variables in foreach loops\r\nfrom (x, y) in coordinateList ...              // range variables in queries\r\nM(out (var x, var y));                         // tuple out parameters\r\n``` \r\n\r\nFor range variables in queries, this would depend on clever use of transparent identifiers over tuples. \r\n\r\nFor out parameters this may require some form of post-call assignment, like VB has for properties passed to out parameters. That may or may not be worth it.\r\n\r\nFor syntax, there are two general approaches: \"Types-with-variables\" or \"types-apart\".\r\n\r\n``` c#\r\n// Types-with-variables:\r\n(string first, string last) = GetName(); // Types specified\r\n(var first, var last) = GetName();       // Types inferred\r\nvar (first, last) = GetName();           // Optional shorthand for all var\r\n\r\n// Types-apart:\r\n(string, string) (first, last) = GetName(); // Types specified\r\nvar (first, last) = GetName();              // All types inferred\r\n(var, var) (first, last) = GetName();       // Optional long hand for types inferred\r\n```\r\n\r\nThis is mostly a matter of intuition and taste. For now we've opted for the types-with-variables approach, allowing the single `var` shorthand. One benefit is that this looks more similar to what we envision deconstruction in tuples to look like. Feedback may change our mind on this.\r\n\r\nMultiple variables won't make sense everywhere. For instance they don't seem appropriate or useful in using-statements. We'll work through the various declaration contexts one by one.\r\n\r\nOther deconstruction questions\r\n------------------------------\r\n\r\nShould it be possible to deconstruct a longer tuple into fewer variables, discarding the rest? As a starting point, we don't think so, until we see scenarios for it.\r\n\r\nShould we allow optional tuple member names on the left hand side of a deconstruction? If you put them in, it would be checked that the corresponding tuple element had the name you expected:\r\n\r\n``` c#\r\n(x: a, y: b) = GetCoordinates(); // Error if names in return tuple aren't x and y \r\n```\r\n\r\nThis may be useful or confusing. It is also something that can be added later. We made no immediate decision on it.\r\n\r\n\r\nTuple conversions\r\n=================\r\n\r\nViewed as generic structs, tuple types aren't inherently covariant. Moreover, struct covariance is not supported by the CLR. And yet it seems entirely reasonable and safe that tuple values be allowed to be assigned to more accommodating tuple types:\r\n\r\n``` c#\r\n(byte, short) t1 = (1, 2);\r\n(int, int t2) = t1; // Why not?\r\n```\r\n\r\nThe intuition is that tuple conversion should be thought of in a *pointwise* manner. A tuple type is convertible (in a given manner) to another if each of their element types are pairwise convertible (in the same manner) to each other.\r\n\r\nHowever, if we are to allow this we need to build it into the language specifically - sometimes implementing tuple assignment as assigning the elements one-by-one, when the CLR doesn't allow the wholesale assignment of the tuple value.\r\n\r\nIn essence we'd be looking at a situation similar to when we introduced nullable value types in C# 2. Those are implemented in terms of generic structs, but the language adds extensive special semantics to these generic structs, allowing operations - including covariant conversions - that do not automatically fall out from the underlying representation.\r\n\r\nThis language-level relaxation comes with some subtle breaks that can happen on upgrade. Consider the following code, where C.dll is a C# 6 consumer of C# 7 libraries A.dll and B.dll:\r\n\r\n``` c#\r\nA.dll:\r\n\r\n(int, long) Foo()... // ValueTuple<int, long> Foo();\r\n\r\nB.dll:\r\n\r\nvoid Bar(object o)\r\nvoid Bar((int?, long?) t)\r\n\r\nC.dll:\r\n\r\nBar(Foo());\r\n```\r\n\r\nBecause C# 6 has no knowledge of tuple conversions, it would pick the first overload of `Bar` for the call. However, when the owner of C.dll upgrades to C# 7, relaxed tuple conversion rules would make the second overload applicable, and a better pick.\r\n\r\nIt is important to note that such breaks are esoteric. Exactly parallel examples could be constructed for when nullable value types were introduced; yet we never saw them in practice. Should they occur they are easy to work around. As long as the underlying type (in our case `ValueTuple<...>`) and the conversion rules are introduced at the same time, the risk of programmers getting them mixed in a dangerous manner is minimal.\r\n\r\nAnother concern is that \"pointwise\" tuple conversions, just like nullable value types, are a pervasive change to the language, that affects many parts of the spec and implementation. Is it worth the trouble? After all it is pretty hard to come up with compelling examples where conversions between two tuple *types* (as opposed to *from* tuple literals or *to* individual variables in a deconstruction) is needed.\r\n\r\nWe feel that tuple conversions are an important part of the intuition around tuples, that they are primarily \"groups of values\" rather than \"values in and of themselves\". It would be highly surprising to developers if these conversions didn't work. Consider the baffling difference between these two pieces of code if tuple conversions didn't work:\r\n\r\n``` c#\r\n(long, long) tuple = (1, 1);\r\n\r\nvar tmp = (1, 1);\r\n(long, long) tuple = tmp; // Doesn't work??!?\r\n```\r\n\r\nAll in all we feel that pointwise tuple conversions are worth the effort. Furthermore it is crucial that they be added at the same time as the tuples themselves. We cannot add them later without significant breaking changes.\r\n\r\nTuples vs ValueTuple\r\n--------------------\r\n\r\nIn accordance with this philosophy we cannot say more about the relationship between language level tuple types and the underlying `ValueTuple<...>` types.\r\n\r\nJust like `Nullable<T>` is equivalent to `T?`, so `ValueTuple<T1, T2, T3>` should be in every way equivalent to the unnamed `(T1, T2, T3)`. That means the pointwise conversions also work when tuple types are specified using the generic syntax.\r\n\r\nIf the tuple is bigger than the limit of 7, the implementation will nest the \"tail\" as a tuple into the eighth element recursively. This nesting is visible by accessing the `Rest` field of a tuple, but that field is considered an implementation detail, and is hidden from e.g. auto-completion, just as the ItemX field names are hidden but allowed when a tuple has named elements.\r\n\r\nA well formed \"big tuple\" will have names `Item1` etc. all the way up to the number of tuple elements, even though the underlying type doesn't physically have those fields directly defined. The same goes for the tuple returned from the `Rest` field, only with the numbers \"shifted\" appropriately. All this says is that the tuple in the `Rest` field is treated the same as all other tuples.\r\n\r\n\r\nDeconstructors and patterns\r\n===========================\r\n\r\nWhether or not we allow arbitrary values to opt in to the unconditional tuple deconstruction described above, we know we want to enable such positional deconstruction in recursive patterns:\r\n\r\n``` c#\r\nif (o is Person(\"George\", var last)) ...\r\n``` \r\n\r\nThe question is: how exactly does a type like `Person` specify how to be positionally deconstructed in such cases? There are a number of dimensions to this question, along with a number of options for each:\r\n\r\n* Static or instance/extension member?\r\n* `GetValues` method or new `is` operator?\r\n* Return tuple or tuple out parameter or several out parameters?\r\n\r\nSelecting between these, we have to observe a number of different tradeoffs:\r\n\r\n1. Overloadable or not?\r\n2. Yields tuple or individual values?\r\n3. Growth path to \"active patterns\"?\r\n4. Can be applied to existing types without modifying them?\r\n\r\nLet's look at these in turn.\r\n\r\nOverloadability\r\n---------------\r\n\r\nIf the multiple extracted values are returned as a tuple from a method (whether static or instance) then that method cannot be overloaded. \r\n\r\n``` c#\r\npublic (string firstName, string lastName) GetValues() { ... }\r\n```\r\n\r\nThe deconstructor is essentially canonical. That may not be a big deal from a usability perspective, but it does hamper the evolution of the type. If it ever adds another member and wants to enable access to it through deconstruction, it needs to *replace* the deconstructor, it cannot just add a new overload. This seems unfortunate.\r\n\r\nA method that yields it results through one or more out parameters can be overloaded in C#. Also, for a new kind of user defined operator we can decide the overloading rule whichever way we like. For instance, conversion operators today can be overloaded on return type.\r\n\r\nTuple or individual values\r\n--------------------------\r\n\r\nIf a deconstructor yields a tuple, then that confers special status to tuples for deconstruction. Essentially tuples would have their own built-in deconstruction mechanism, and all other types would defer to those by supplying a tuple.\r\n\r\nEven if we rely on multiple out parameters, tuples cannot just use the same mechanism. In order to do so, long tuples would need to be enhanced by the compiler with an implementation that hides the nested nature of such tuples.\r\n\r\nThere doesn't seem to be any strong benefit to yielding a single tuple over multiple values (in out parameters). \r\n\r\nGrowing up to active patterns\r\n-----------------------------\r\n\r\nThere's a proposal where one type gets to specify deconstruction semantics for another, along even with logic to determine whether the pattern applies or not. We do not plan to support that in the first go-around, but it is worth considering whether the deconstruction mechanism lends itself to such an extension.\r\n\r\nIn order to do so it would need to be static (so that it can specify behavior for an object of *another* type), and would benefit from an out-parameter-based approach, so that the return position could be reserved for returning a boolean when the pattern is conditional.\r\n\r\nThere is a lot of speculation involved in making such concessions now, and we could reasonably rely on our future selves to invent a separate specification mechanism for active patterns without us having to accommodate it now. \r\n\r\n\r\n\r\nConclusion\r\n----------\r\n\r\nThis was an exploration of the design space. The actual decision is left to a future meeting.\r\n\r\n\r\nOut vars and their scope\r\n========================\r\n\r\nWe are in favor of reviving a restricted version of the declaration expressions that were considered for C# 6. This would allow methods following the TryFoo pattern to behave similarly to the new pattern-based is-expressions in conditions:\r\n\r\n``` c#\r\nif (int.TryParse(s, out var i) && i > 0) ...\r\n```\r\n\r\nWe call these \"out vars\", even though they are perfectly fine to specify a type. The scope rules for variables introduced in such contexts would be the same as variables coming from a pattern: they generally be in scope within all of the nearest enclosing statement, except when that is an if-statement, where they would not be in scope in the else-branch.\r\n\r\nOn top of that there are some relatively esoteric positions we need to decide on.\r\n\r\nIf an out var occurs in a *field initializer*, where should it be in scope? Just within the declarator where it occurs, not even in subsequent declarators of the same field declaration.\r\n\r\nIf an out var occurs in a *constructor initializer* (`this(...)` or `base(...)`) where should it be in scope? Let's not even allow that - there's no way you could have written equivalent code yourself."},"comment":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/comments/216713388","html_url":"https://github.com/dotnet/roslyn/issues/11031#issuecomment-216713388","issue_url":"https://api.github.com/repos/dotnet/roslyn/issues/11031","id":216713388,"user":{"login":"alrz","id":3105979,"avatar_url":"https://avatars.githubusercontent.com/u/3105979?v=3","gravatar_id":"","url":"https://api.github.com/users/alrz","html_url":"https://github.com/alrz","followers_url":"https://api.github.com/users/alrz/followers","following_url":"https://api.github.com/users/alrz/following{/other_user}","gists_url":"https://api.github.com/users/alrz/gists{/gist_id}","starred_url":"https://api.github.com/users/alrz/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alrz/subscriptions","organizations_url":"https://api.github.com/users/alrz/orgs","repos_url":"https://api.github.com/users/alrz/repos","events_url":"https://api.github.com/users/alrz/events{/privacy}","received_events_url":"https://api.github.com/users/alrz/received_events","type":"User","site_admin":false},"created_at":"2016-05-04T01:04:14Z","updated_at":"2016-05-04T01:04:14Z","body":"**Tuple syntax for other types:** There are two aspects to this:\r\n\r\n1. Omission of the *type* in the object creation (#35)\r\n2. Omission of the `new` keyword which is somehow related the *type invocation* previously proposed for records.\r\n\r\nBoth of these at the same time would of course only make sense for tuples. In my opinion, using tuple literals to construct arbitrary types is not a good idea. I'd suggest to keep option 2 here and for other types consider #35 syntax e.g. `T t = new (...);` and *type invocation* will be reserved for records.\r\n\r\nNot to mentioned option 4 seems weird when we want to consider making any type deconstructible with positional patterns rel. \"Deconstructors and patterns\" so how is that any different?\r\n\r\n\r\n**Tuple deconstruction:** While \"assignments\" is a good addition to this list, I don't quite understand (yet) the need for different syntaxes for declaration and pattern-matching i.e. `let` statement. I think allowing *complete patterns* in all of those cases would be safe and useful, e.g.\r\n\r\n```cs\r\nforeach(let (var x, var y) in tuples)\r\nforeach(let {Result is var result} in tasks)\r\n```\r\nI'm aware that property patterns are not up for vnext but the above code can be simplified to the following via [identifier patterns](https://github.com/dotnet/roslyn/issues/10624#issuecomment-212401863) (a la Swift):\r\n```cs\r\nforeach(let (x, y) in tasks)\r\nforeach(let {Result: result} in tasks)\r\n```\r\nYou may argue that one might not be able to use constants here but the fact that any constant would make this a fallible pattern is enough to be sure that no one would ever use constants in this context.\r\n\r\n\r\n\r\n>Should it be possible to deconstruct a longer tuple into fewer variables, discarding the rest?\r\n\r\nI think it would make sense to use wildcards if one is not interested in all tuple members. So there is no need to implicitly discard the rest (assuming that we're using `let` instead of declarators).\r\n\r\n>Should we allow optional tuple member names on the left hand side of a deconstruction?\r\n\r\nI don't get it. Earlier in the post you've said\r\n>That seems contrary to this being about deconstructing not constructing tuples!\r\n\r\nSo I do not expect to be able to use  tuple member names on the LHS, because they will be generic lvalue expressions, and there ain't no \"tuple member names\" in that. \r\n\r\n\r\n\r\n\r\n\r\n\r\n>A well formed \"big tuple\" will have names `Item1` etc. all the way up to the number of tuple elements, even though the underlying type doesn't physically have those fields directly defined. \r\n\r\nIf I have a *default* `ItemX` I expect to get those names from reflection, too. As an alternative we could keep those runtime names untouched and use Rust's syntax `tuple.0`, `tuple.1` to get corresponding fields when we don't specify any names, regardless of the actual length of the flattened tuple.\r\n\r\n"}}