meetings/2016/LDM-2016-07-13.md
We resolved a number of questions related to tuples and deconstruction, and one around equality of floating point values in pattern matching.
For tuple element names occurring in partial type declarations, we will require the names to be the same.
partial class C : IEnumerable<(string name, int age)> { ... }
partial class C : IEnumerable<(string fullname, int)> { ... } // error: names must be specified and the same
For tuple element names in overridden signatures, and when identity convertible interfaces conflict, there are two camps:
interface I1 : IEnumerable<(int a, int b)> {}
interface I2 : IEnumerable<(int c, int d)> {}
interface I3 : I1, I2 {} // what comes out when you enumerate?
class C : I1 { public IEnumerator<(int e, int f)> GetEnumerator() {} } // what comes out when you enumerate?
We'll go with the strict approach, barring any challenges we find with it. We think helping folks stay on the straight and narrow here is the most helpful. If we discover that this is prohibitive for important scenarios we haven't though of, it will be possible to loosen the rules in later releases.
Should it be possible to deconstruct tuple literals directly, even if they don't have a "natural" type?
(string x, byte y, var z) = (null, 1, 2);
(string x, byte y) t = (null, 1);
Intuitively the former should work just like the latter, with the added ability to handle point-wise var inference.
It should also work for deconstructing assignments:
string x;
byte y;
(x, y) = (null, 1);
(x, y) = (y, x); // swap!
It should all work. Even though there never observably is a physical tuple in existence (it can be thought of as a series of point-wise assignments), semantics should correspond to introducing a fake tuple type, then imposing it on the RHS.
This means that the evaluation order is "breadth first":
This approach ensures that you can use the feature for swapping variables (x, y) = (y, x);!
(var x, var y) = GetTuple(); // works
(var x, var y) t = GetTuple(): // should it work?
No. We will keep var as a thing to introduce local variables only, not members, elements or otherwise. For now at least.
We decided that deconstructing assignment should still be an expression. As a stop gap we said that its type could be void. This still grammatically allows code like this:
for (... ;; (current, next) = (next, next.Next)) { ... }
We'd like the result of such a deconstructing assignment to be a tuple, not void. This feels like a compatible change we can make later, and we are open to it not making it into C# 7.0, but longer term we think that the result of a deconstructing assignment should be a tuple. Of course a compiler should feel free to not actually construct that tuple in the overwhelming majority of cases where the result of the assignment expression is not used.
The normal semantics of assignment is that the result is the value of the LHS after assignment. With this in mind we will interpret the deconstruction in the LHS as a tuple: it will have the values and types of each of the variables in the LHS. It will not have element names. (If that is important, we could add a syntax for that later, but we don't think it is).
Deconstruction and conversion are similar in some ways - deconstruction feels a bit like a conversion to a tuple. Should those be unified somehow?
We think no. the existence of a Deconstruct method should not imply conversion: implicit conversion should always be explicitly specified, because it comes with so many implications.
We could consider letting user defined implicit conversion imply Deconstruct. It leads to some convenience, but makes for a less clean correspondence with consumption code.
Let's keep it separate. If you want a type to be both deconstructable and convertible to tuple, you need to specify both.
Should they implement Deconstruct and ITuple, and be convertible to tuples?
No. There are no really valuable scenarios for moving them forward. Wherever that may seem desirable, it seems tuples themselves would be a better solution.
We should allow deconstruction to feature wildcards, so you don't need to specify dummy variables.
The syntax for a wildcard is *. This is an independent feature, and we realize it may be bumped to post 7.0.
pair += (1, 2);
No.
What equality should we use when switching on floats and doubles?
== - then case NaN wouldn't match anything..Equals, which is similar except treating NaNs as equal.The former struggles with "at what static type"? The latter is defined independently of that. The former would equate 1 and 1.0, as well as byte 1 and int 1 (if applied to non-floating types as well). The latter won't.
With the latter we'd feel free to optimize the boxing and call of Equals away with knowledge of the semantics.
Let's do .Equals.