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.
Other languages use variations over the notion of tuples for this. Maybe C# should too.
This proposal follows up on #98 and addresses #102 and #307.
The 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.
While 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.
Your options today include:
_Out parameters:_
``` c#
public void Tally(IEnumerable
int s, c;
Tally(myValues, out s, out c);
Console.WriteLine($"Sum: {s}, count: {c}");
This 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.
On the bright side, because the results are out parameters, they have names, which help indicate which is which.
_System.Tuple:_
``` c#
public Tuple<int, int> Tally(IEnumerable<int> values) { ... }
var t = Tally(myValues);
Console.WriteLine($"Sum: {t.Item1}, count: {t.Item2}");
This 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.
_Declared transport type_
``` c#
public struct TallyResult { public int Sum; public int Count; }
public TallyResult Tally(IEnumerable
var t = Tally(myValues);
Console.WriteLine($"Sum: {t.Sum}, count: {t.Count}");
This 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.
The 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.
# Tuple syntax
If 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.
## Tuple types
Tuple types would be introduced with syntax very similar to a parameter list:
``` c#
public (int sum, int count) Tally(IEnumerable<int> values) { ... }
var t = Tally(myValues);
Console.WriteLine($"Sum: {t.sum}, count: {t.count}");
The syntax (int sum, int count)
indicates an anonymous struct type with public fields of the given names and types.
Note 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.
This is fully compatible with async:
``` c#
public async Task<(int sum, int count)> TallyAsync(IEnumerable
var t = await TallyAsync(myValues);
Console.WriteLine($"Sum: {t.sum}, count: {t.count}");
## Tuple literals
With no further syntax additions to C#, tuple values could be created as
``` c#
var t = new (int sum, int count) { sum = 0, count = 0 };
Of 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.
Creating a tuple value of a known target type, should enable leaving out the member names:
``` c#
public (int sum, int count) Tally(IEnumerable
{
var s = 0; var c = 0;
foreach (var value in values) { s += value; c++; }
return (s, c); // target typed to (int sum, int count)
}
Using named arguments as a syntax analogy it may also be possible to give the names of the tuple fields directly in the literal:
``` c#
public (int sum, int count) Tally(IEnumerable<int> values)
{
var res = (sum: 0, count: 0); // infer tuple type from names and values
foreach (var value in values) { res.sum += value; res.count++; }
return res;
}
Which syntax you use would depend on whether the context provides a target type.
Since 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.
Languages with tuple features typically use a deconstruction syntax to receive and "split out" a tuple in one fell swoop:
``` c#
(var sum, var count) = Tally(myValues); // deconstruct result
Console.WriteLine($"Sum: {sum}, count: {count}");
This way there's no evidence in the code that a tuple ever _existed_.
# Details
That's the general gist of the proposal. Here are a ton of details to think through in the design process.
## Struct or class
As 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.
Arguably, 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.
In 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.
Structs also have a number of other benefits, which will become obvious in the following.
## Mutability
Should 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.
Now 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.
If tuples are used as fields, then those fields can be readonly if desired.
## Value semantics
Structs 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.
## Tuples as fields
While 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.
Care needs to be taken with mutable structs in the heap: if multiple threads can mutate, tearing can happen.
## Conversions
On top of the member-wise conversions implied by target typing, we can certainly allow implicit conversions between tuple types themselves.
Specifically, 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.
You 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.
``` c#
(double sum, long count) weaken = Tally(...); // why not?
(int s, int c) rename = Tally(...) // why not?
One 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!
It 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.
Even 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).
This needs further investigation. What would it take to implement tuple unification? Is it worth the price? Are tuples worth doing without it?
There'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:
``` c#
(var sum, var count) = Tally(myValues); // deconstruct into fresh variables
(sum, count) = Tally(otherValues); // deconstruct into existing variables?
In other words is the form `(_, _, _) = e;` a declaration statement, an assignment expression, or something in between?
This discussion intersects meaningfully with #254, declaration expressions.
## Relationship with anonymous types
Since 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:
- 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.
- There could be deconstruction syntax for them.
# Optional enhancements
Once in the language, there are additional conveniences that you can imagine adding for tuples.
## Tuple members in scope in method body
One (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:
``` c#
public (int sum, int count) Tally(IEnumerable<int> values)
{
sum = 0; count = 0;
foreach (var value in values) { sum += value; count++; }
}
Just 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.
This 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.
If 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:
``` c#
public double Avg(int sum, int count) => count==0 ? 0 : sum/count;
Console.WriteLine($"Avg: {Avg(Tally(myValues))}");
Here, `Tally` returns a tuple of type `(int sum, int count)` that gets splatted to the two arguments to `Avg`.
Conversely, 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.
I 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:
``` c#
var list = List<(string name, int age)>();
list.Add("John Doe", 66); // "unsplatting" to a tuple
There are probably a lot of details to figure out with the splatting and unsplatting rules.
So much :+1:. A lot of useful applications for this.
I'd vote yes for unification across assemblies, as there could be legitimate cases where being able to return a Tuple would best match the intentions of an library's API (eg. multiple returns).
Destruction into existing variables might confuse developers, but I could see many cases where you might want it. (ex:)
int counter;
bool shouldDoThing;
try {
(counter, shouldDoThing) = MyMethod(param);
} catch (Exception ex) {
// Handle exception
}
Tuple members in scope sounds very useful. How would it handle cases like return yield
though? Just wouldn't be allowed?
I think I'm against having a Tuple be able to be implicitly "splat". I'd be a much bigger fan of a javascript-esque spread operator so rather than
public double Avg(int sum, int count) => count==0 ? 0 : sum/count;
Console.WriteLine($"Avg: {Avg(Tally(myValues))}");
you'd do
public double Avg(int sum, int count) => count==0 ? 0 : sum/count;
Console.WriteLine($"Avg: {Avg(...Tally(myValues))}");
or something similar. In the grand scheme of things though, this may not be nearly as confusing to developers as I'm thinking.
out
parameters to methods with a tuple return type, so instead of let result = dictionary.TryGetValue(key, &value)
you can just write let (result, value) = dictionary.TryGetValue(key)
. That might be worth considering so that old APIs can automatically take advantage of the new syntax. The order of the elements in the tuple should probably following the order of appearance in the method signature; that is, the actual return value first, following by all out parameters in sequence.(int p1, string p2)
for tuples and {int P1, string P2}
for anonymous types. It's unrelated to tuples, but I'd also like to see such syntactic sugar for delegates, so that I can write, as in C++, Func<int(int, int)>
or maybe even with parameter names Func<int(int p1, int p2)>
. Or with a tuple return type Func<(int r1, int r2)(int p1, int p2)>
.(int p1, string p2)
suffers the same deficiency as the record proposal: Parameters start with lower case characters, whereas the resulting properties should be upper case, so that you can access the tuple with tuple.MyProperty
instead of tuple.myProperty
. Probably a unified solution should be considered for both. Though I don't like the solution of the record proposal (namely, to specify both names in the form of int x : X
); I'd much rather prefer to declare the parameter lower case and have them converted to upper case automatically. It's true that you encode coding style into the compiler that way, but who isn't using the default .NET naming conventions anyway?I think the proposal is generally good; however, some issues that came to mind:
immutable
or readonly
keyword. readonly (int sum, int count)
.int sum Sum(int a, int b) { sum = a + b; }
Even so, I'm on the fence about the whole feature as the lack of an explicit return
might make debugging more hairy.Actually simple value types may not incur the cost associated with copying as they are good candidates for inlining.
I think simple cases like (var sum, var count) = Tally(myList)
could very well be optimized into simply pushing two values onto the stack, and then popping them into the new variables, so there would be no tuple creation overhead.
Can we use "empty tuple"? If the empty tuple ()
is allowed, we can use it as Void or so-called Unit type which solves the issue #234 without CLR enhancement.
() M() => (); // instread of void M() {}
Func<()> f = () => (); // instead of Action
@ufcpp: Great idea!
Just a thought, but is it possible to use the System.Tuple<>
type(s) for this?
(int a, int b) M((int sum, int count) x)
could be transformed to
[return: TupleMemberNames("a", "b")] Tuple<int, int> M([TupleMemberNames("sum", "count")] Tuple<int, int> x)
Of couse, this would not work straight with generics, but I imagine that problem is rather close to the object/dynamic differentiation that is already implemented.
This idea does mean that the return value is not a value type, but it does solve the unification between assemblies issue.
@erik-kallen The disadvantages of the existing tuple types is that
Item1
and Item2
, etc.@gafter I probably wasn't clear enough when I wrote what I did, but my intention was that when the compiler encounters these special attributes it could create a temporary type which is has a runtime type of System.Tuple
, but with aliases for the members so if you have a parameter declared with [TupleMemberNames("sum", "count")] Tuple<int, int> x
, then the access x.a
would be translated to x.Item1
by the compiler, and the source code needs not care that it is actually a System.Tuple
.
I acknowledge the reference type thing to be an issue in my idea, though.
@erik-kallen We're actively exploring the idea of "erasing" the member names using attributes as you suggest. However we're leaning toward using struct versions of tuples.
How would this be supported in lambdas? For example, I have this method:
public static void DoSomething(Func<string, (string x, string y)> arg)
With type-inference, the argument looks like this:
(a) => { return (x=a, y=a); }
Is the return type always inferred? If not, you get something really weird:
(string x, string y) (a) => { return (x=a, y=a); }
@ryanbl They would be target-typed. In the absence of a target type
Regardless of being a value or reference type, as @erik-kallen, I envision tuples to be like System.Tuple...
The diference between tuples and anonymous types is that anonymous types are types with properties with well defined names and types that you can pass around to libraries like ASP.NET routing and Dapper and tuples have well defined properties (Item1,Item2, ...) that can be aliased at compile time.
However, in order to make them useful for function return values, the compiler could apply an attribute with the compile time names, like parameter names are part of the function signature.
Another thought- while not directly related to tuples, the idea that the compiler can provide a strongly-named way of using tuples (avoiding Item1 and Item2) seems like it could be extended to making a more strongly named sort of dictionary (where the items have more meaningful names than .Key and .Value).
I often run into situations where I need to index some collection on more than one dimension and you're forced to either build custom types or use multiple dictionaries which might share the exact same type signature. If that happens then the only differentiation is in the variable name and possibly XML comments.
Example:
``` C#
class Baz {
public String LongName {get; private set;}
public String ShortName {get; private set;}
...
}
void Foo(IEnumerable
var lookup = enumerable.ToDictionary(e => e.ShortName, e => e);
...
Bar(lookup);
}
void Bar(IDictionary
//Now what did lookup index by? LongName or ShortName? I need to check calling code or documentation to know.
}
```
@MgSam This proposal explicitly describes support for tuples with named members.
I feel it would be important for tuples to work across assemblies. I could think of a few places in most of the projects I've had at work that would be improved by this proposal, and in almost every case it's in an interface implemented in one assembly and consumed in another. Whether or not the existing Tuple classes are used, serious consideration should be made that this proposal allows for exposure of tuple returns across assemblies.
Maybe it'd be good to add struct analogues to the existing classes?
@gafter I know, I just thought the problem was similar enough to warrant mentioning here. There really isn't much of a distinction in Github between proposal threads and discussion threads and in any case I don't have a good enough solution in mind to make a separate proposal.
Some of this has been said, so some of this is +1 to those comments :-). My thoughts (albeit colored by a decade plus of hacking Common Lisp) ...
Do not conflate immutability and tuples, let me decide independently how they are used because I know sometimes I want to mutate some state.
Do use structs because a primary use is Multiple Value Return (MVR), and that should be efficient. In the same way spurious boxing affected Roslyn perf, an inner loop using MVR could become a perf impact in a large but responsive application.
I can't believe I'm asking for more syntax, but please consider "return values(...)" akin to return yield, where I make it very clear to the reader I'm return multiple values. Though I do admit the parenthesis and no comma op is almost manifest enough, but I feel I want "return values" :-).
Tuples will definitely be used beyond MVR. We often have little carrier or terd types that sucks to have to name and make more first class. Consider in one place in my program I need a list of Foo's, and it turns out I'd like to timestamp the items in the list. The timestamp is meta to the abstraction of being a Foo, and I really don't want to declare FooWithTimeStampForSinglePlaceUsage :-). Note too, in this scenario I want to mutate the timestamp with user activity.
Do support for productivity partially supplied tuple elements and allow me to deconstruct to fewer vars than elements. Perhaps in the MVR case I can declare default values for elements if they are not supplied, or you just default to default(T). This works well too with your idea of using named arg like syntax for filling in SOME of the elements and defaulting others. OFTEN when using MVR you do not need all the values returns because the extra values are only sometimes helpful. Imagine Truncate returns the Floor of x/y and a remainder, but 90% of the time I just need the first value of the integer division. It may be too much for C#, but I'd also consider if I having a deconstructing binding site with more variables declared than the expression returns, then you just fill in my extra values with default(T) ... I'll fill them in with actual values in the next few lines of code, but now I have the tuple I want already in hand without an intermediary needed.
I didn't think too deeply, but it seems some sort of unification across assms is needed for a smooth MVR experience (where these may be used the most).
I'd also unify with anon types at least to the extent of implicit conversions (note, this would be for productivity coding, but yes, too much convenience in the hands of the masses can lead to too much inefficiency in code :-)).
I really think what you call "tuple members in scope" is VERY NOT C#. It smacks of an expression based language (which C# is not) where falling off functions returns the last value of the last expression of whatever branch you were in. It is also very subtle for C#, and I think the MVR feature should be a bit more explicit, like 'ref', for ready readability.
I like adding splatting, but I think it should be explicit (a la funcall vs. apply in Common Lisp, or * in python). I get we already to some not readily readable resolution around paramarrays, but I'd strongly consider breaking from the precedent here for manifest readability.
Thanks for listening!
Bill
@billchi-ms, some proposals for C#7 make it look like we should never have to write types again. but we should stay away of that temptation.
I like the proposal and I think it should be optimized for MRV because it's what we don't have today. Many are using the current Tuple types whit the readability penalties and that should be improved.
I've recently started using Python to work on an existing code base and I find value on tuples as return types. Other than that, I'm a bit cautious.
Regarding this:
public (int sum, int count) Tally(IEnumerable<int> values) { ... }
why not express it like this instead?
public {int sum, int count} Tally(IEnumerable<int> values) { ... }
curly braces express the returned structure, parentheses are perceived rather as function arguments.
@armenmk I think I'd prefer parens to curlies for the returns. Most languages (that I've dealt with, anyway) that allow for multiple returns use parens on the left side for placing the return values into variables, and curly braces always indicate blocks of code.
Like this a lot.
public (int sum, int count) Tally(IEnumerable<int> values)
{
sum = 0; count = 0;
foreach (var value in values) { sum += value; count++; }
}
Among others it decouples the flow definition from the returned value(s). Remember how often you create a local variable, assign/reassign it in the function and then return. This is it, but shorter and less verbose.
There has actually been a request for multiple return values for some time. I'm not a big fan of tuples but rather built in syntax for supporting multiple return values. Take a look at this conversation line for more ideas folks have had: http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2083753-return-multiple-values-from-functions-effortlessly
Some function definitions like:
public {[default]bool success, MyClass2 Class2} MyFunction();
or
public static TryParseResult {bool Success,int value, string Error} Int32.TryParse(string value);
perhaps
This would allow default handling if desired to just use default values and support backward compatibility for methods that changed to support multiple types.
So something like:
if (Int32.TryParse(mystring)) //would still work
But also
var ret=Int32.TryParse(...)
would offer the ability to access the error string if desired.
Providing functional refactoring capability as well as the ability to provide additional information as part of a return if needed.
Not sure whether people read the discussion over uservoice so I'll just post my suggestion here.
For a very long, long time I've been jealous with some languages that can just return multiple values out of a function, especially the way it's implemented in Ruby and Lua it's just beyond amazing and simple.
Here is the way we can take advtange over this feature in Lua
function GetPoint2D(x, y)
-- Do something with x and y
return x, y
end
local x, y = GetPoint2D(1, 2)
I know we can use Tuples, Arrays, ref/out and whatnot to do the same thing but they all have too few pros if at all and many cons in the context of this problem and I'll elaborate.
Tuples saves you from creating a new class to hold some values but then the properties are unnamed.
So we can already access var in the local scope of the function and get everything to work nicely, we just need a way to expose the anonymous type that the compiler creates and I thought that it makes sense to use the var keyword to do it.
It would be really nice to have something along these lines.
public var GetPoint2D(float x, float y)
{
// Do something with x and y
return new { X = x, Y = y };
}
And then the usage is quite simple and trivial.
var point = GetPoint2D();
I'm not sure what are the challenges here and whether it's possible but it can be quite amazing to have a solution for this rather than all these hackish approaches that clutter the code and make maintainability and everything else quite hard.
I have a couple of comments about this subject.
A previous example shows well what I mean.
This code increments count uselessly if not needed.
public (int sum, int count) Tally(IEnumerable<int> values)
{
sum = 0; count = 0;
foreach (var value in values) { sum += value; count++; }
}
If polymorphism could take into account return types as well, one could write a second method
public int Tally(IEnumerable<int> values)
{
int sum = 0;
foreach (var value in values) { sum += value; }
}
So from the user side one could write
(sum, count) = Tally(intEnum);
and the compiler would select the first version
sum = Tally(intEnum);
and the compiler would select the second one.
Really like this idea. I did use DynamicObject
to create Python's namedtuple
construct using classes, but of course it lacks intellisense and isn't a language construct, just a class.
Love it!.
Does the destructuring/deconstruction syntax require you to use the names of the fields of the tuple exactly?
That certainly doesn't feel symmetric to invoking a method. You don't have to name your local variables the same names as a method's parameters, if you want to pass them as arguments.
I really value that you want to name the tuple's values to give them meaning, but it's very easy to conceive of scenarios where I want to call a new-tuple returning method, but I already happen to have a local variable with a conflicting name.
It seems like deconstruction should allow callers to escape the names that the method author originally chose. Deconstruction seems like a very caller-focused feature already, anyway.
IMO, the comma should be an operator rather than a syntactic separator. The names of the values aren't really useful as much as their positions. "Declaring" them in the function signature has merit, IMO, but declaring their types seems like duplication. Example:
private (,,,) MyTupleReturningFunction(int p1, string p2) {
long val1;
int val2;
...
return val1, val2, val1 + val2 / 42;
}
Less typing (both with the keyboard and declarations), and I think that's pretty obvious what's happening. I think this strikes a good balance between documenting the function's return and having to declare every little thing. Having to declare a named return "parameter" in the function signature and set that variable explicitly within the scope of the function seems a bit clunky and duplicative since the real power behind the Tuple is its position, not its name. The types of the return types don't need to be declared both in the function signature and whatever variable is being returned, IMO; that can be discovered from the return statement.
Also, it would be nice if the compiler allowed less variables to be assigned on function return than the function provides (using the prior function signature):
var ephemeral1, var ephemeral2 = MyTupleReturningFunction(val1, val2);
The opposite (allowing more returns than the function provides) could cause problems during refactorings later, as well as non-obvious null/default values during runtime, which could cause hard to find bugs if/when the developer misreads the function signature's return.
Also, it would be nice to allow short-cutting with var like that so that the types all of the return values don't need to be declared. The prior example would become:
var ephemeral1, ephemeral2 = MyTupleReturningFunction(val1, val2);
Great proposal! One thing I would like to see in addition to already proposed features is ability to omit certain returned values. Sometimes I am interested in only some values and language should not force me to name other variables that I don't care about:
(var sum, _) = Tally(myValues); // I do not care about the second value.
I like the idea of making them value types, to avoid allocations and such.
I'm not convinced that named tuple elements are the right choice, though. This could instead be implemented with a struct version of System.Tuple<...> (System.ValueTuple<...> ?), and some syntactic sugar.
names make them more self-documenting, but at the cost of verbosity, compiler complexity, and trouble crossing assembly boundaries.
-same as System.Tuple<...>
, but a value type
-implement it for 0-8 generic parameters (or more)
-as a type, (T0,T1)
becomes System.ValueTuple<T0,T1>
-as an l value, (a,b) = x;
becomes var newSymbol = x; a = newSymbol.Item1; b = newSymbol.Item2;
-as an r value, (a,b)
becomes ValueTuple.Create(a,b)
for splatting: it might make sense to have explicit syntax for splatting. so, var x = (a,b); foo(x)
doesn't get any special treatment, but var x = (a,b); foo(@x)
would get splatted.
I like this but i'd also like the syntax to be more similar to anon types, like using { string Name, int Age}
as you suggest. Or maybe that's the distinction, {...} = class, (...) = struct/tuple
Also, how about having a keyword that lets the compiler infer the return type? That would allow you to return an anon type and that would then be a tuple (class/struct discussion aside)
Consider this
public magicKeyword GetFruit(){
return new {Name = "Banana", Color = "Yellow"}
}
This would also fit in very well with linq, i've quite often found my self wanting to return something that selects an anon type, and to me this is very similar to the tuples we're talking about here, at least conceptually.
Maybe to avoid language colissions we could have
public ! GetFruit(){
return new {Name = "Banana", Color = "Yellow"}
}
since it's an upside down "i" as in inferred :)
or maybe
public ? GetFruit(){
return new {Name = "Banana", Color = "Yellow"}
}
or
public <> GetFruit(){
return new {Name = "Banana", Color = "Yellow"}
}
Maybe that's mosts inline with the rest of the language, like when you describe a generic type without a type argument, typeof(List<>)
i'm sorry if this going of topic for this proposal but it feels relevant to me :)
Where can I vote something like "GOD no microsoft dont do that please"?
@UnstableMutex, when you would have a valid reason and a constructive comment then maybe someone will listen, besides, no one is forcing you to use it, heck, you can even remove it yourself! it's open source, you know...
So I'll go head and ask you why in the world you wouldn't want it?
While I am happy to see a language I love growing, I am concerned that it seems to be growing in all different directions. Do we really need to add more syntax, making the language harder to read and optimize?
In my experience, if you group something once, you'll end up grouping it again. If you group it more than once, you should declare a structure or class. We are, after all, working in an object-oriented language. Following that pattern, you will be able to write good code that requires less maintenance later when you want to add more values, or to add a method. It adds more context and transportability.
Not to be melodramatic, but we are on our way to becoming PERL with proposals like this. PERL also has lots of special syntax for writing various minute things succinctly and "elegantly".
@eyalesh, while I agree with you that @UnstableMutex was not being constructive, "why not?" Is also not a constructive comment, nor is it a justification for the need to add this.
C# is not Ruby. C# does not function the same way internally. If you prefer Ruby, that's fine! It's a fun language, and I'm sure you could switch over and work primarily in Ruby.
While nobody is forced to USE features, we are forced to deal with code written by others using these features for bad reason.
@kbirger, You're focus too much on the syntax (that you have to learn and read) and that people may abuse it but what about the important issues like maintenance? productivity? or actually making the code more readable? are these not real concerns? for two minor points you made I made three major ones.
I doubt people want to write objects that are not part of the actual domain of the software, not to mention maintenance such code! developers don't want to create temporary objects just to hold values or making unreadable arrays or tuples!
Just because you're using an object-oriented language doesn't mean you need to have primitive features in the language, by your own definition let's not use any of the features and keep it as primitive as possible!
Still, I'll correct you, C# is not just an object-oriented language but a multi-paradigm language, meaning, it supports more concepts of programming style.
You need to do the math but the more primitive you are, you need to work more, maintain more and read more code! just like in math x^2 = x * x, now raise the power and work harder writing these multiplications... I prefer using the short version and understand the long one.
I don't get this argument where developed are amazed that they actually need to learn new things and new concepts, so let me break it to you as a developer, your second job is to expand your horizons.
I don't program in Ruby and I have no intention to do so, among many programming languages that I use, there are 5 that I really love and C# is on the top of the list, so I want nothing but for the language to be better and improve.
Don't be so focus on your own needs, some people have other needs and they are real, just like yours.
guys, sorry for my non constructive comment i have details to add to @kbirger message. i'll write it.
@kbirger Ruby is not the only language that supports tuples or named tuples. Quite a lot of newer languages have built-in tuple syntax and some form of pattern matching or destructuring. Besides, F# functions the same way as C# internally (Common Language Infrastructure) and nobody complains about tuple support in F#.
In my opinion, being able to group related data into structures without giving it a name is a big benefit. When writing complex enterprise business logic, I am always struggling to find appropriate names for simple tuple-like classes that are used internally only in a few places. The alternative of using Tuple's Item1... ItemN properties doesn't make my code more readable.
Additionally, since scripting is finally coming to C#, tuples will only improve the experience of it. The REPL becomes less useful if you have to write a lot boilerplate declaration code.
In some situations this feature can even enable useful utility functions. Quite often I find myself writing code like below:
var fooTask = GetFooAsync();
var barTask = GetBarAsync();
await Task.WhenAll(fooTask, barTask);
// using fooTask.Result and barTask.Result multiple times creates more noise
in contrast to C# version with tuples support
// foo and bar are not Task<> instances
(var foo, var bar) = await TaskEx.WhenAll(GetFooAsync(), GetBarAsync());
// implementation of TaskEx.WhenAll
public async (T1, T2) WhenAll<T1, T2>(Task<T1> task1, Task<T2> task2)
{
await Task.WhenAll(task1, task2);
return (task1.Result, task2.Result);
}
@vas6ili I think being able to group related data into structures without giving it a name Is a huge mistake. what if you break SRP principle? are you sure that you need one object not two or more? if i cant think out name of the class i consider i need some refactoring...
yes I dont use tuple class, i dont use anonimous types because of if i use tuple'string,'string as example after few month i dont want to remember what string is Item1 and what is Item2
Another reason to avoid any tuples is missed xml commenting ability on it
I writed how: I'll repeat: if you cant name some class probably you must divide it into few small classes.
@eyalesh Please keep it civil.
@eyalesh We're here to do the right thing for users, no matter what the motives. Questioning motives is only one form of ad hominem argument you used; ad hominem attacks are unwelcome.
@gafter, It was never my intention but each person is entitled to his own opinions so you can take think whatever you like, best regards.
p.s. I've deleted the irrelevant posts.
@UnstableMutex In some situations it is a bad design, in others not. I don't think library developers will start replacing classes with named tuples if this feature gets into c# vnext. Sometimes the name of the class is defined by its shape and structure and I'd rather have (string customerName, int nrOfVisits)
then CustomerNameAndNrOfVisits
class. Especially in application code, I find myself creating a lot of these simple record-like classes and having built-in support helps a lot. The abundance of these kind of classes only makes it more difficult to browse and understand a new project. Of course, as any feature, it will be abused, but this is not a sufficient reason for not having it.
are you sure that you need one object not two or more?
Adding a new item to the tuple will actually force me to change every calling site which might be beneficial. A new class member might be overlooked without some "Find All References" help in Visual Studio.
I writed how: I'll repeat: if you cant name some class probably you must divide it into few small classes.
I don't see how splitting into smaller classes is a solution when I have to return 2 simple values
@vas6ili In some situations it is a bad design, in others not.
Agreed.
I don't see how splitting into smaller classes is a solution when I have to return 2 simple values.
Agreed if simple values.
but if class name pattern is somethingANDotherthing I shall always think that breaks srp.
in the customer example I think in general:
class Customer
{string Name}
class VisitInfo
{Customer, Visitnumber}
(what about adding surname to customer? what about adding Patronymic, gender, casing in future)
this two classes would be better. but there I can make mistake - i dont know BL. but again if I 'm going to name class somethingANDotherthing i think twice.
@UnstableMutex, The name of the class doesn't violate SRP per se, what violates it is when you design a class that has a well defined responsibility and after awhile it doesn't match its original design and do more than it was intended for.
SRP is the product of a design and implementation mismatch but this discussion is about having a feature that will allow you to reduce the amount of classes you implement, especially temporary ones and private classes that do not represent anything _useful_ but carry data.
Now, imagine you have some tiny private classes with some variations that carry data and are used by the same class, naming them differently can be troublesome and annoying, it can also make some APIs quite ugly if you need to expose these classes.
@eyalesh yes The name of the class doesn't violate SRP, but name like that says that class may be violate srp.
I dont know reasons to reduce amount of classes in general. class is unit of OOP like var, property, method. Are you often reduce variable number?
but I think we're going offtopic.
About topic feature: If that will be implemented I want ability to fast refactor tuple to class...
even better if it will be under "experimental" checkbox and/or under red warning !!!are you sure?!!!
There are many ways to skin this cat. Today you return multiple values this way
T Mode<T>(this IEnumerable<T> data, out int occurrences) { ... }
...
int occurrences;
var mostFrequent = arguments.Mode(out occurrences);
The most annoying thing about this is that you do not stay in "expression mode" but have to use statements, which may interrupt the flow of your computation.
With tuples you can return multiple values this way
(T mode, int occurrences) Mode<T>(this IEnumerable<T> data) { ... }
...
(string mostFrequent, int occurrences) = arguments.Mode();
but maybe we'd allow you to use that invocation syntax for the existing method in the first example, above. It isn't clear if the last line is a statement form or an expression (technically, expression-statement).
With records and pattern matching you'd do it something like this
class ModeAndOccurrences<T>(T Mode, int Occurrences);
ModeAndOccurrences<T> Mode<T>(this IEnumerable<T> data) { ... }
...
if (arguments.Mode() is ModeAndOccurrences<T>(string mostFrequent, int occurrences)) ...
Perhaps you could use pattern matching with the tuple form
(T mode, int occurrences) Mode<T>(this IEnumerable<T> data) { ... }
...
if (arguments.Mode() is (string mostFrequent, int occurrences)) ...
TL;DR: Conversions - be careful
There's lots to like here, but there's one specific "why not?" this proposal asks that has a good answer I'd like to provide.
``` C#
(double sum, long count) weaken = Tally(...); // why not?
(int s, int c) rename = Tally(...) // why not?
In particular that second line is not a good idea. One of the strengths of this proposal is that tuple members are named, and that's excellent for type safety. After all:
``` C#
(int x, int y) rename = Reposition(...)
(int row, int col) rename = Reposition(...)
Typically, y
would be equivalant to row
and x
to col
, yet this code implicitly and accidentally reverses their order. This is a really nasty bug, and it's one that can easily be introduced by a refactoring.
Even the original Tally
example suffers from this problem:
C#
(int sum, int count) rename = Tally(...) // why not?
(int count, int sum) rename = Tally(...) // why not?
There's something to be said that the original ordering (sum, count) isn't as good as the ordering (count, sum) since you might view this sum of various powers - 0 is the count, and 1 is the sum.
In any case, once you introduce a rename like this, then any changes to the names in the right-hand-side tuple silently cause semantics errors that aren't detected at compile nor runtime.
For this reason I think that conversions should not be able to implicitly change member names - any name-changing conversions should explicitly need to mention the original name.
@EamonNerbonne, good point! ;)
@eyalesh you made one great point, and it seals my opinion in contradiction.
Understanding. The more "STUFF" we put into a language, the less of the language the average programmer will understand. There is a cost and benefit to adding a feature.
Delegates were a fantastic add, but most people you talk to will not understand how they are implemented, how delegate caching and compilation works, and the slight performance penalty associated with executing a delegate vs a virtual method, vs a non-virtual method.
Delegates let you do things you could not easily do before without making mountains of difficult-to-maintain code (Single-use interface implementations, etc)
Adding something which generates code in order to save you from writing grouping classes seems much lower on the benefit curve, but almost just the same on the cost curve, because you're generating classes, or structs, that will do hidden boxing and unboxing and other non-obvious operations.
multi-paradigm language
C# is an object oriented language with elements from other paradigms in it. Adding bells and whistles does not change the core. The core of the engine is types. I would even go so far to say that it is a TYPE oriented language, where JavaScript is an object oriented language. Python is an object oriented as well, whose type system was added later. We don't need to add every feature from every language into C# just because in some cases it would make our lives more convenient in the short term.
@EamonNerbonne Makes a good point indeed. This problem is easily solved by using a struct. It's explicit, portable, and readable. It makes your code extensible and future-proof. (You can add methods, change it to a class, add extension methods, abstract it, etc). Code evolves.
This sort of feature is cute and it has its uses in interpreted languages where it is organic and mostly "free", but I simply do not see enough value for the cost of adding yet another convenience feature that generates code.
@kbirger, I understand the cost but in the same sense this feature can make our life easier!
You see, just because "some" developers don't know what's a delegate not to mention what's a callback or pointer to a function doesn't mean it isn't useful for many others.
You're saying that delegates are great and then continue to say that "Adding something which generates code in order to save you from writing grouping classes seems much lower on the benefit curve" you do realize that a delegate is just a class wrapping a function? without them we could actually achieve exactly the same thing with classes? it just require a bit of extra work.
I don't know anything about hidden Unboxing and Boxing, it depends on many things.
You can't say that the language is object-oriented and then say that it contains elements from other paradigms because it's just like saying it's a multi-paradigm but I'll let you read it from the official documentations.
Python and JavaScript like C# are a multi-paradigm language!
C#: https://msdn.microsoft.com/en-us/vstudio/hh341490.aspx
Python: https://docs.python.org/2/howto/functional.html
JavaScript: https://en.wikipedia.org/wiki/ECMAScript (notice the paradigms on the side? Object-Oriented is not one of them.) whether it's Object-Oriented paradigm is very controversial although ECMAScript 6 is going to fix that, I guess.
Unlike these languages above, Smalltalk is a true Object-Oriented Language in the _true_ sense of the word but in today's world you can't do much with a single paradigm of programming.
A language is either statically typed or dynamically typed language whether it's Object-Oriented is irrelevant to this story because it's a different story so I'm not sure about Type Object Oriented language, never heard this term before.
Again, I think it's a great feature but I guess you have a different option about it. :)
@EamonNerbonne I think your point is moot because it's no different than a function declaration. When you call a function, the parameters aren't named (usually, though of course you can call functions with named parameters these days), and can be just as easily switched by mistake. For example:
public int MyFunction(int stuff, int otherStuff) { ... }
The call:
MyFunction(var1, var2);
Var1 and Var2 can be switched very easily, and yet people seem to be able to avoid mostly making that mistake so I doubt this would be any different.
I reiterate my request, nay, plead, that this feature, if implemented, NOT name the parameters in the declaration and instead derive the values by POSITION from the return statement within the body of the function. Preferably, without explicitly setting the types of the return values in the function declaration, since they're not necessary, either. Again, for example:
private (,) MyFunction(...) {
...
return 23, "Stuff";
}
or, if absolutely necessary, type the return parameters, but leave out the names:
private int, string MyFunction(...) {
...
return 23, "Stuff";
// Or, as would most likely be the case
// return var1, var2;
}
In this case, I don't believe the parens are necessary since the comma will make it obvious what's going on to the parser. However, an argument could be made that parens would be make it more consistent with the function's parameter list. I think this syntax, though, makes it more consistent with a single value return function, which is higher priority, IMO.
IMO, there's absolutely no reason whatsoever to name the return parameters. Further, the call to either of these would be:
var x, y = MyFunction(...);
Types will be inferred from the return statement in the body or, if necessary, from the declaration (if that route is chosen). On the call, I don't think there's any reason for parens, nor any real reason to declare the types (since, as I said, that can be inferred deterministically), so var can be used, and instead of doing var, var, var, a single var should suffice.
As long as this feature is being considered, which is really about easing programmer workload IMO, as much of the syntax as possible should be simple and as consistent as possible with existing constructs. They should strive to eliminate all unnecessary syntax (parens, names, etc.).
@AZBob, Yeah, I didn't like the fact that they decided or still deciding whether to go with named tuples but I'd take this over nothing! :)
I really like the following syntax you posted:
private int, string MyFunction(...) {
...
return 23, "Stuff";
// Or, as would most likely be the case
// return var1, var2;
}
@AZBob, this is not valid syntax and is confusing:
var x, y = MyFunction(...);
You can only declare variables in this comma delimited way if they are of the same type, e.g.
int x, y;
The 'var' keyword is only compiler magic so you don't have to type out 'int' since it can be inferred. So unless they start allowing this:
int x, long y;
I think the syntax you provide will be confusing.
I personally don't like this feature in the context of returning values from a function. I would prefer that instead these are two separate features. I like the idea of using compiler magic to make 'out' parameters have a cleaner syntax for returning multiple values from a function, so instead of using this:
int MyFunction(out int v2, out int v3)
{
v2 = 2;
v3 = 3;
return 1;
}
int v1, v2, v3;
v1 = MyFunction(out v2, out v3);
You would use this:
(int, int, int) MyFunction()
{
return 1, 2, 3;
}
int v1, v2, v3;
v1, v2, v3 = MyFunction();
@taylorjonl, obviously it's not a valid syntax, it's a proposal...
A similar syntax exists in Lua and it's very straightforward and not confusing.
They can reuse the var keyword if they wish, I don't think it will ambiguously match something else but they can also decide to just use 'var x = MyFunc(...)' and then have the compiler generate an anonymous type, much like they are planning on doing in this very proposal and then the var keyword would infer to this anonymous type but they decided to go with a different syntax, probably to support named tuples and kill two birds at the same time.
The out/ref parameter was never meant to be used to return multiple values and there are cases that you can't actually use it so abusing a feature to have multiple return values and have some magic done by the compiler is a really bad idea.
@eyalesh, how is it not meant to return multiple values? That is 'out's entire reason for existing. What cases can't you use 'out' to return multiple values?
I understand Lua accepts that syntax but it is no C#ish. I would hate to have this language turn into some combination of every language out there. If this proposal goes forth with the proposed syntax they would have to allow:
int v1, long v2, string v3 = MyFunction(...)
Or they have to say you have to use 'var', which is not a good exception just for this use case in my opinion.
Personally, I would prefer the syntax
var (x, y) = GetCoordinates();
@AZBob I disagree, I think having the returned tuple's components be named at the declaration site is a very good idea. While you are correct that people rarely make the mistake of passing the arguments to a function in the wrong order, they can always look at the function declaration if there is any ambiguity.
@taylorjonl, The question is not about whether you can but whether you should...
I don't get the not C#-ish part... really.
Seems like annotating the variables with the actual return type is counterproductive, tedious and I'd argue that it's even error-prone.
In fact, you know what? I'd go with it and say let the types be there for these who want them but let us have a succinct syntax for multiple return values and var is a great candidate.
'out' exists to remove the burden of initializing the variable before the function call, yes you can abuse it and yes the docs says it's useful to use it to return multiple values.
The reason they added 'ref/out' is mostly for interoperability, it's used heavily in COM and this was probably the main reason for having them.
Out got too many drawbacks, you can't use it with async, can't use it with yield and you can't pass properties directly to it!
Sometimes you just want to pass something and mutate the copy for whatever reason, especially when it comes to value types.
Last but not least out is really ugly even though there was/is a proposal to make it nicer #254.
Does this proposal cover array destructuring? (examples here)
I also like the simplified syntax for array literals.
Could either tuples or anonymous classes cast to property-only interfaces as in TypeScript?
This would allow to get rid of unnecessary classes in many cases.
It seems like this would require you to write conversions through operator overloading. It seems unclean to have to do a runtime check on whether or not the target type is "property-only", moreover you would still have to execute the constructor, so you wouldn't be able to guarantee that two objects created via ctor(x,y) are identical.
However, if we think of this not as casting but as creation such that tuple (x,y) is cast to type Foo where Foo has constructor Foo(x,y), then perhaps the conversion could be performed that way, and you would simply get the standard "Type Foo does not contain a constructor with specified arguments" message if you try to cast the wrong tuple to Foo.
Though my gut says that if you're considering this, then you should just go with real classes anyway. (See points above)
Possibly a first-time response, but a full specification of the return type in the signature makes it hard to read. Just to throw something out there, maybe generic constraint syntax could be employed, really just as an aliasing mechanism. For example:
public R Tally(IEnumerable<int> values)
where R : { int sum, int count }
{
var s = 0; var c = 0;
foreach (var value in values) { s += value; c++; }
return new R { sum = s, count = c };
}
Maybe the language designers would prefer a reserved symbol for the return type to distinguish from type parameters (or something like R{}) but this may not be necessary:
public R FirstLast<T>(IEnumerable<T> source)
where R : { T first, T last }
{
...
}
But most importantly we need to settle on the correct pronunciation of Tuple. I am firmly in the "oo" camp.
@kevinhector
What, you don't pronounce it "toe-play" (ˈtō-ˌplā)? You weirdo.
Mixing it with the generic syntax, especially when it's not related to generics, doesn't seem right to me. I'd rather have the expanded (and frankly unattractive) syntax because maybe that will dissuade people from using them haphazardly instead of defining proper public types.
When there are two I call it a Twople.
I've always called it "row-like thingy".
@HaloFour
If it makes you feel any better we can express this in a new meta syntax which may enlist in the genericness of the method:
public ? BiggestSmallest<T>(IEnumerable<T> source)
returns : { T biggest, T smallest }
where T : IComparable<T>
{
...
}
Not sure this makes me feel any better, but it is certainly implementable in Roslyn
@gafter
When there are three you call it a Fewple?
@kevinhector Twople, Threeple, Fourple, etc.
:-1: The return types should be in the same position that they are now. I think that the proposed syntax (from Mads) is much cleaner.
BTW, does anyone know the answer to my question?
@glen-84 No, nobody knows the answer to your question.
@gafter more than four should be called 'alotple'.
@GeirGrusom 'maniple', how could there be a different option?
On a more serious note, will advanced sugared tuples remove the need for record classes?
Record classes would have additional members generated to support pattern matching, right?
I know Oxford would never accept this word, but maybe "multi-ple"?
@orthoxerox Re "will advanced sugared tuples remove the need for record classes"
The tuples we envision would be structs. Records can be structs or classes, can be mutable, can have additional members and implement interfaces, and have a name. The record feature may fulfill some of the use cases that motivated tuples, but there are use cases for either that the other doesn't help with.
One thing that would be nice is to allow tuple deconstruction in LINQ queries. That way, we might be able to write code like:
``` c#
from (x, i) in collection.WithIndexes()
…
Or:
``` c#
from item in items
let (sum, count) = Tally(item.Values)
…
Interesting to compare this proposal with the equivalent EcmaScript 6 feature (and other languages, goes without saying), where deconstruction, for example, extends the syntax for array and object literals:
var [m, d, y] = [3, 14, 1977];
function today() { return { d: 6, m: 2, y: 2013 }; }
var { m: month, y: year } = today(); // month = 2, year = 2013
http://ariya.ofilabs.com/2013/02/es6-and-destructuring-assignment.html
So not exactly tuples here, but similar deconstruction concepts using the existing basic JS types.
Notice how the second case doesn't suffer from the order ambiguity that @EamonNerbonne mentioned above.
Also of notice: the "var" keyword is outside of the declaration. In C#, it's of course a little different as there are cases where you'll want to be explicit about the individual types of the members of the tuple.
It's better to make names optional and just define types, something like this (int,int)
and when you return some values you don't have to repeat all that and just return (5,6);
and names will be given only when deconstruction occurs (var item1, var item2) = ...
. as you can see in the other functional languages.
I have just one question what about delegates, expression and Reflection?
Could I do?
Func<Tuple<int,int> func = Tally;
How can i get the names of return values from MethodInfo
One way would be an attribute attached to the return of the method.
@cordasfilip Our current thinking is that we'd convey tuple names using attributes, similar to the scheme we currently use to distinguish object
from dynamic
.
:+1:
Reflection support is awesome, but will it be possible to get the compile-time type information:
var tupleType = typeof((int sum, int count));
It could be applicable in custom attributes:
[MyDefaultValue(typeof((int sum, int count)), "10,15")]
...
I also feel value in cross-assembly unification (and of course field naming). One of the scenarios for me would be decoupled flexible messaging (Erlang-style):
public class Actor
{
public void Handle(Message message)
{
// Could be improved by pattern matching I guess...
// No need for sharing the message type - complete decoupling:
if (message.Name == "UpdatePriceAndLabel" && message.Value.GetType() == typeof((decimal price, string label)))
{
// logic
}
}
}
public class Message { public string Name {get; set;} public object Value {get; set;} }
@dsaf
The (runtime) type of (int sum, int count)
is the type (int, int)
.
If you have a tuple value
object t = (1,21m, "foo");
You'll be able to switch on the runtime type of its elements
if (t is (decimal price, string label)) ...
This composes with other pattern forms. I believe the code you want will be something like
if (message is {Name is "UpdatePriceAndLabel", Value is (decimal price, string label)}) ...
@gafter that makes sense, thank you.
Our current thinking is that we'd convey tuple names using attributes, similar to the scheme we currently use to distinguish object from dynamic.
Will those attributes be preserved into runtime, so that it would be possible to explore them via reflection?
How else would tooling pick them up if they weren't on the metadata?
@paulomorgado they're going to have to be in some metadata somewhere, but the question is whether that'll be in complile-time metadata (i.e. roslyn) or runtime (i.e. System.Reflection). If it's in reflection metadata, that means that tuples that differ only in names will need to differ in runtime types, which means that the runtime type of (int sum, int count)
cannot be just (int, int)
to avoid clashing with (int count, int sum)
.
I'm failing to understand your point, @EamonNerbonne. Can you give an example of cross assembly compile-time metadata?
@paulomorgado it's an asinine example, but plain File.ReadAllBytes(path-to-assembly)
is available to the compiler, but not (necessarily) available to reflection. The compiler can choose to embed all kinds of data there, even if it's not exposed to reflection. It could (not saying that it should!) embed tuple type names there, or whatever information it wants to.
One practical example is for instance the IL-stream: you cannot read the IL of a method using reflection, to the best of my knowledge, which has the (to me) unfortunate downside that you cannot reliably decompile a delegate. (I'd love that ability for ExpressionToCode). The compiler can (in the unlikely event that it needs to).
Hum....
First make tuples implement a series of interfaces like ITuple<T1>
, ITuple<T1, T2>
, etc.
Next add anonymous structs like this.
new struct {
Name1 = t1,
Name2 = t2
}
Make anonymous structs and classes implement ITuple<...>
implementing Item1, Item2, etc in order of declaration. The Name1 (as in the example above) is used to implement Item1, etc.
Next make new value type tuples named ValueTuple<T1>
, ValueTuple<T2>
etc and make implement the same interfaces also.
With this, we can have either named or anonymous tuples that are either reference types or value types. Second, named and anonymous tuples would work together easier.
The (runtime) type of (int sum, int count) is the type (int, int).
This brings up another question, will the compiler be smart enough to know that my (int, int)
is identical to your (int, int)
and not provide multiple definitions for them or will we have to rely on runtime checking?
@whoisj I just don't understand why should it has names for tuple items at all. it has to ignore them in the deconstruction. it's not tuple anymore, it's more of a record type. there's something that I'm missing?
@alrz
Per @gafter comment, it seems they are discreet types. This a typeof()
should result in a comparable value. No?
@whoisj I didn't quite understand what you meant by "comparable", but let's say they should be identical. the ability to specify names for members is so confusing I cannot stand!
@whoisj Doesn't _Unification across assemblies_ section cover this?
@alrz
...the ability to specify names for members is so confusing I cannot stand!..
This feature will not be obligatory to use.
@dsaf If a feature is really confusing (and I'm not saying this one is), suggesting that people should just avoid it doesn't really address the concern. It will be obligatory to _read_ all of the features used in code that I am reading, whether I elect to use the feature or not. We will avoid adding to the language features that we believe are too confusing.
We believe we may specify this feature in a way that isn't too confusing.
The runtime type (int, int)
would be a synonym for (a different way of writing) a type something like System.StructTuple<int, int>
.
@gafter That's fair, although I have seen experienced people thinking that e.g. var
is confusing (always / sometimes). I hope that C# team will continue using their best judgement and not listen to any of us too much :). A C# 7 CTP 1 would be a great way of checking whether names are _that_ confusing in practice :).
PS: is there a fresh Language Design Review on the horizon? Topics like this one (at 100+ posts) are becoming hard to read and summarize (I realize that I am not helping here :).
Doesn't Unification across assemblies section cover this?
I don't know, does it?
The runtime type
(int, int)
would be a synonym for (a different way of writing) a type something likeSystem.StructTuple<int, int>
.
OK, happy now - thanks :smile:
For me the "dream" (you always have to start with the dream) would be:
var Tally(IEnumerable<int> values)
{
var sum = 0;
var count = 0;
foreach (var value in values) { s += value; c++; }
return sum, count;
}
var sum, count = Tally(values);
var query = from x, y in left.Zip(right)
select x * y;
Now I realize there are a lot of implications and "why nots" in the way of this dream, but I'll try my best to clear up those that seem solvable/reasonable so far:
1) Why var?
In my opinion, this would be basically saying to the compiler "apply to my method the same type inference rules as for lambdas". In lambdas you only need to know the input types, and the output type can often be inferred directly from the lambda body.
In cases where you cannot do the inference, you would specify the output types directly like so:
int, int Tally(IEnumerable<int> values);
2) Why no parenthesis?
I have to admit that my first honest reason is "because it feels right".
The second reason is that I share the feeling above that parenthesis are already overloaded for too many things (priority, method calls)... it feels like reading C# will soon approximate trying to understand LISP with the difference that LISP would have more consistent semantics...
3) What about type declarations?
It seems one of the main issues with this syntax is it seems to imply changes to type declarations. Specifically, it would allow for:
var x, y = 5, "hello";
int x, string y = 5, "hello";
I don't see this as too much of a problem, really. First, this change is fully backwards compatible, as this is a superset of allowed type declarations where in previous versions the types in the declaration list were forced to be identical. Second, is there any reason other than historical that such mixed declaration lists are not allowed?
It looks like possible ambiguous cases would be something like:
var x, y = MyFunction(); // am I assigning "y" or both "x,y"
Which seems like it could be solved by sensible resolution rules, plus a way to disambiguate manually (for example):
var x = 0, y = MyFunction() // I am definitely assigning "y"
This last example is not great but also raises another important question: how to handle direct assignment of the function result (i.e. var o = MyFunction()
)? I would say in this case there are two options:
A) Default to positional semantics (i.e. generate properties Item1, Item2, etc, like tuples). These can be assigned meaningful names in subsequent deconstruction statements.
B) Specify "default" names in the return statement (different ways):
var MyFunction()
{
// assume returned identifiers provide the default names (similar to anonymous types)
return result, flag;
// explicit naming (like anonymous types), will have to be consistent across all return statements
return result = 0, flag = true;
}
I would prefer B) in this case.
I know there are probably many more implications, but I tend to prefer to settle on the "dream" very clearly from the beginning. In my experience you never see the elegant solutions to difficult problems until you've persevered enough in trying to solve them, and that perseverence only comes after you see the dream clearly.
Anyway, my 2c and sorry for the long post.
would it be possible to deconstruct the tuple into other places like this?
(HttpContext.Current.Items["key"], this.SomeOtherProperty) = tuple;
I want to propose using {} instead of () for Tuple
@Thaina What is the justification/motivation? Every other relevant language _ever_ - F#, Scala, Nemerle, Rust, Swift, Python - uses round brackets for tuples. This is if we forget about potential parsing confusion.
Using ()
also makes sense because this syntax is already used by another part of the language for a nearly identical concept: method arguments.
I would prefer that the option is there to drop the parenthesis altogether; it makes for much more readable code IMHO (e.g. see Python and my post above). Maybe they will be necessary to disambiguation in some cases and to build nested tuples, but would be great to have the shorthand version to make it more readable and it doesn't look like it introduces much parser ambiguity.
@glopesdev it seems that you are talking about decomposition rather than tuples though?
@dsaf yes, mostly dropping the parenthesis is useful for tuple deconstruction, but it could also be applied to type declaration:
int, int p = 4, 2; // explicit tuple type variable declaration
var p = 4, 2; // type inference version
public int, int Func(int x) // function returning tuple (explicit type)
{
return x, x * x;
}
public var Func(int x) // function returning tuple (type inference)
{
return x, x * x;
}
Because in C# we already has anonymous object which is new { }
Using { } is more like structural data than argument. ( ) is used for argument function and type cast so it not feel right being tuple
Dropping parentheses is also better than ( ). But I prefer { } to ensure block
But var is too vague. We can't even var on field yet
In fact, I'm starting to think tuples should not have names at all, but rather their semantics should always be given entirely by deconstruction. You shouldn't need to worry about matching the names of arguments, only their position, the same as argument lists.
This would solve the dilemmas mentioned above about renaming and matching tuple members, and would also solve the unification problem since all tuples would be unified under the same value type. These problems simply disappear by universally using the deconstruction syntax.
In fact, if tuples are being introduced might as well unify them with method argument lists (the "splatten" scenario, mentioned above). This should allow for plenty of optimizations, since often these tuples will already be stack-allocated anyway.
As long as you can deconstruct flexibly, you can even simplify the use of more complex types like IEnumerable<int, int>
.
Here's some more complex examples:
var array = new[] { (4, 3), (1, 5), (8, 9) }; // array of int, int tuples
foreach (var x, y in array)
{
// Do stuff
}
// Lambda example (here we need disambiguation with parenthesis I guess?)
array.Select((x, y) => x * y);
// splatten example
void MyFunc(int value, string text);
var callList = 5, "hello";
MyFunc(*callList); //we would need some operator for "splatten", borrowing Python's for now
As I mentioned earlier, method arguments are tuples. They are optionally named, so why shouldn't C# use the same syntax for tuples as method arguments when they are the same thing anyway? Why invent something new when there is already a precedence for this in the language?
@GeirGrusom because the usage is different, and that means it has differing consequences on code readability and especially maintainability. Also, just because method arguments work that way doesn't mean it's a good idea; and now's the time to learn from past experience.
Consider that most methods argument tuples have just one place in code where the tuple is "unpacked" and that there is no way to abstract away the tuple as a whole. This means that confusion surrounding which argument means what is limited. You can look up the name of that third argument trivially by going to the method definition, or by writing tooling (i.e. intellisense) to do that for you. Nevertheless, standard advice is to limit the number of arguments to avoid confusion.
General tuples are much worse in this regard. They're first class objects and that's a _downside_ here because it means it's not at all clear where that tuple came from - there may be several layers of indirection between tuple construction and deconstruction, including runtime indirection that make finding the tuple member "names" virtually impossible. Sure, you can avoid layers of indirection, but imposing that restriction undermines pretty much the most basic programming tenet - encapsulation. In essence: you don't want to impose roadblocks to extracting methods or interfaces.
As such, it's very unwise to use positional tuples anywhere that indirection might occur, which is so many places that it's probably not a concept worth having at all. Confusing a tuple (row, col)
with (x,y)
is easily done and leads to nasty bugs. Is it (sum, count)
or (count, sum)
? (min, max)
or (max, min)
?
Named tuple members are much more human-friendly, and provide useful compile-time checks as to whether you've sanely unpacked that tuple. If you're going to develop a codebase with more than one person over a period of more than a few weeks, such static checks are a real lifesaver.
Tuples with positional members (i.e. like method arguments) are an anti-feature C# should definitely avoid. Named tuple members are the way to go.
To simplified
( )
is argument
new { }
is anonymous object
What is closer to tuples?
By syntax;
var i = { } // This feel like object
var i = ( ) // Feel more like lambda expression
Tuple are not just a bunch of arguments It IS a GROUP of arguments
Just arguments is naked individual. Tuple is block struct, a container of arguments
So I prefer to have Tuple alike to anonymous object differ by new { }
and plain { }
In other word. I think anonymous object is reference type tuple while the new tuple is valuetype so it should just use same syntax
@EamonNerbonne I get your argument re. named tuples. It makes sense. However, I also agree with @GeirGrusom that we already have method argument tuples. The examples you mention of swapping x, y
with row, col
pretty much already exist in method calls, and I believe the problems of maintainability and readability are exactly the same...
I understand we may want to learn from the past, but if we now introduce a "better" method for _output_ tuple resolution, why shouldn't this method be enforced for _input_ tuples as well? Now it becomes an unfortunate dichotomy...
If we would change it, this would be a massive break with the past, as positional arguments have been the standard since the days of C.
On the other hand, if we change it just for _output_ tuples then the asymmetry itself will be damaging. Suddenly you start getting protection when outputting multiple arguments; but then you need to unpack these named arguments into positional arguments when passing the result to another function... how should this assigment be made? Named arguments? Should we let tuples be passed into functions only if their names match? What about name clashing and swapping between different libraries? I think the issues are enormous...
I think unfortunately we still don't have a satisfactory alternative to positional _input_ arguments, and as long as that's the case, I would use the exact same semantics for _output_ arguments. Anything else seems like it will raise problematic questions.
@glopesdev at heart that's a tradeoff between superficial consistency, and improving the language. Personally, I don't think this kind of consistency is really worth anything. There's no concrete advantage to programmers in making positional arguments look like tuple definitions (C's function pointer syntax sort of point out the disadvantages of symmetry for symmetries sake). The only value in looking like C is to easily attract early adopters (and C# is way past that point). Also: symmetry cuts both ways. Right now, C# objects have members with names - why introduce such a major inconsistency for a feature with so little immediate value?
You state that the problems where x, y
can by confused with row, col
are identical in the method call situation, but the post you replied to points out that this is _not_ the case. Method arguments tend to be easy to find because they're not first class objects. That means finding the meaning of the n-th argument is generally as easy as go-to-definition or intellisense (or some similar bit of tooling help). By contrast, this proposal aims give tuples that first class status. The usage of a tuple may well be removed from the creation of a tuple by any number of layers, virtual method calls, overrides etc. - it will not be possible to find the construction site of any given tuple variable automatically. Positional tuples are a serious maintenance problem; while positional method arguments aren't quite as problematic (and even those aren't ideal).
I think it bears emphasizing that one of C#'s strengths today is large, long-lived applications. Lots of features and tools cooperate to make C# friendly to the maintenance programmer. A code base that uses positional tuples is going to be noticably harder to maintain. Undermining a key selling feature of C# sounds like bad trade-off for some syntactic brevity, especially if there are perfectly reasonable alternatives (such as named-member tuples).
You suggest that positional tuples are more easily transparently unpacked into function arguments. However, this isn't as trivial as you make it out to be: since tuples are first-class, this means you won't _always_ want to unpack them; there will be cases wherein a tuple is just one of the positional arguments. Doing this automatically is almost certainly going to expose many corner cases with things like optional arguments and params arrays. Furthermore, C# already has the concept of named parameters. If there's a possibility of matching based on position, why not match based on name? In fact, that's likely to be _easier_ to understand because the possibilities for confusion are much smaller - even params arrays and optional arguments have names, so it's clearer which tuple member corresponds to which argument should you want automatic implicit unpacking (which I don't think is necessarily a good idea for a first version of tuples in the language).
To summarize:
The key point for me is that named members will result in more maintainable code.
@EamonNerbonne you make good points, and I admit it makes me wonder whether we should be tackling this feature at all...
Would it be possible to have a conservative first step where the consumer of a function is always forced to unpack the return values? This would be symmetrical (sorry, I love symmetries) to the way method lists work right now. In this way you would ensure that you can always figure out the meaning (and order) of tuple arguments as you remove the layers of indirection. But anyway, that would make this less useful for LINQ queries, so nevermind...
I agree with you about C# and maintainability, but getting an answer to all these issues would definitely be a big step forward in having statically typed languages that will flow as easy as dynamic ones.
After some more reading I really like named tuples but the syntax is a bit verbose, I really hope that there's some plans to have syntactic sugar for it.
Something like the following can be really nice.
public (sum, count) Tally(IEnumerable<int> values)
{
// ...
return (s, c);
}
Or
public var (sum, count) Tally(IEnumerable<int> values)
{
// ...
return (s, c);
}
Another attempt but I don't really like it, in fact I don't think it even make sense but oh well...
public (var sum, count) Tally(IEnumerable<int> values)
{
// ...
return (s, c);
}
Named tuples are absolutely a must. It avoids many issues present in other implementations of tuples and is an excellent idea in general.
@eyalsk
I really dislike the suggested type inference in your examples. This sort of approach can be reasonable in languages like C++ (C++ 14 added something similar) but that language has a completely different model of linking and compilation. In C#, the method body may not be available to the consuming code at all.
Anyway this would be a separate and significant feature proposal: namely return type inference for methods.
@aluanhaddad, you're right but I'm really concerned because resulting a tuple is going to be painful to look at from an aesthetics point of view of the code.
C# already has so many information loaded on the method's body that it seems like some methods will look like a very long train where you can't see the forest for the trees where you maintain a bunch of these methods or even individually, the method signature can be quite long.
I have a new idea using interfaces so I'll make a new proposal for this.
EDIT: Actually after some thought interfaces wouldn't make sense.
If anything I think it looking unattractive would be a good thing. Tuples are a nice little convenience but they are not a replacement for proper types and I for one plan on banning their use on my teams for anything with a public-facing contract.
I actually do think that there is a nice symmetry to named tuples in a method definition, though. Feels very much like a parameter list. And since people argue that the C/C++/C# parameter list is in of itself a tuple, then syntactically it makes a lot of sense.
public (int x, int y, int z) Something(int a, int b, int c) { ... }
I for one plan on banning their use on my teams for anything with a public-facing contract.
since people argue that the C/C++/C# parameter list is in of itself a tuple, then syntactically it makes a lot of sense.
:+1:
This abatraction also makes it easier for #5058 to be applicable.
I for one plan on banning their use on my teams for anything with a public-facing contract.
This is actually a really interesting notion to consider... is it too absurd to allow the new proposed tuple syntax for method return types only for private
(and perhaps internal
) methods? Obviously you could "just" return Tuple<T1,T2>
or whatever same as today, but disallowing the new syntax on public methods is a fascinating concept. It'd be encoding that usage pattern right into the compiler.
I can't tell if this is stupid, clever, or stupid-clever.
@HaloFour yeah I agree, it's like I use var when it makes sense but I don't think I agree that it should look unattractive so people won't use it because that's not a valid reason, at least imo.
A valid reason would be to use it when it make sense, var is quite attractive and still most people are smart and educated not to abuse it, don't you think?
Ack; turned into more of a rant than I had intended. tl;dr IMHO + tuples + public = ew
Not all shorthand is created equal. I like var
and I appreciate that it's very specific inference limitations ensure that the compiler isn't doing some crazy acrobatics that would make the code difficult to follow. It also doesn't pollute contracts in any form so I don't have to worry at all that some minor change in one place will cause a domino effect that will result in a breaking change which might go unnoticed until a dependent project is compiled.
I appreciate that var
and tuples exist to solve a similar problem; not having to define lots of tiny one-off types to represent projections. But var
projections are not intended to escape where they are defined and used. Tuples, as a part of a method signature, are explicitly designed to escape.
_In my opinion_ those contracts are definitely on the wrong side of the terse/succinct ratio. Seeing a return type of ValueType<int, int, string, foo, bar>
and then having to depend on someone having written some kind of documentation to explain what that means (we're all devs, we know how laughable that is) seems beyond bad idea to me. Even with names (which survive only in attribute metadata form) that doesn't make for a clear and intuitive public surface. I have enough trouble forcing decent naming conventions from developers who seem to think that keystrokes cost them money or that code should be a write-one-read-never proposition.
I absolutely love eliminating unnecessary keystrokes, but I also know very well that code will have to be read by a human significantly more frequently than it will be written and I try to optimize for that case.
And let me reiterate, this is _my opinion_. I don't care for tuples, at least not in the way these proposals plan on implementing them. The idea that sometime in the future there will be a ton of poorly documented libraries tossing around arbitrary tuples of values does not appeal to me.
@HaloFour, the way I see this is I will never expose tuples through my public APIs but internally they can make many things easier, so I guess we think alike. :)
@HaloFour
Seeing a return type of
ValueType<int, int, string, foo, bar>
of course it's just wrong. maybe they should've used a record type to expose this much of data. as far as I know tuples are used mostly for "data flow" scenarios, e.g. monadic parsers. in functional languages, you can know the intended usage of functions just by looking at their signatures. you won't use tuples for just everything! this makes named items for tuples unnecessary.
Please think about return Tuple of ref and ref of Tuple too
Generic can't have ref in generic parameter but if we allow syntactic tuple it then possible
@Thaina Not sure I understand what you mean. Method parameters are ref
, not generic type arguments. How does ref
fit into tuples?
So you don't know about the "return ref" feature?
@Thaina That's what you're referring to? Considering that tuples would be normal structs if #118 was implemented I don't see why it wouldn't work with tuples. Although if "Tuple of ref" is intended to mean ValueTuple<ref int>
that wouldn't be possible. Generic arguments can't be ref
, it fundamentally changes what IL you can use against that type.
Generic is not possible that's what I already said
That's why if we like to let it possible for tuple. It need to be
(ref int i,float f) Function() { }
or {ref int i,float f} Function() { }
Or the explode way
(ref int,float) Function() { }
///
var i,f = Function();
@Thaina As proposed the tuple (int i, float f)
would only be shorthand for ValueTuple<int, float>
. I don't know how you could get ref
properties on a tuple without some serious changes to this proposal.
Because I don't completely agree with your proposed that's why. There is possibility of tuple we may want to use which is differ from just let it being ValueTuple<> generic. Such as; if it is ValueTuple<> generic we can't specify it field name to i and f. It must be int t1 float t2 because generic struct must be strongly named
Only appropriate is to create anonymous struct feature, not reuse shorthand like that
@Thaina That's not my proposal, that is the C# team's current proposal. They list their reason for not generating a new struct
per tuple, primarily the lack of type equivalence. Even if tuples weren't ValueTuple<>
and were instead their own structs I don't think ref
would be possible because I'm fairly sure that you can't have a field of ref T
or T&
in verifiable code.
I'm wondering if there is any need to introduce the concept of tuples into the language. The operations applicable to a tuple should be applicable to all other types (named classes, anonymous classes, structs) as well.
public (int sum, int count) Tally(IEnumerable<int> values)
{
// Reuse anonymous type syntax
return new { Sum = values.Sum(), Count = values.Count() };
}
////
private class PrivateClass
{
public int Sum { get; set; }
public int Count { get; set; }
}
public (int sum, int count) Tally(IEnumerable<int> values)
{
var dontExposePrivate = new PrivateClass { Sum = values.Sum(), Count = values.Count() };
return dontExposePrivate;
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
////
public User GetUser()
{
return new User { Id = 2, Name = "abc" };
}
////
// Deconstruction on a class
(int id, string name) = GetUser();
Assert.AreEqual(id, 2);
Assert.AreEqual(name, "abc");
// Implicit conversion without loss of precision should be allowed. Works just like individual assignments.
(double sum, long count) = new { Sum = 0, Count = 0 };
// Type inference in LINQ
IEnumerable<(int id, string name)> users = from (id, name) in Enumerable.Range(0, 10).Select(index => GetUser()) // I always felt like doing List<var> for LINQ.
select new { Id = id, Name = name + "Clone" };
public User CreateUser(int id, string name)
{
return new User { Id = id, Name = name };
}
////
var splatMe = new { Id = 4, Name = "Splat" };
User user = CreateUser(params splatMe); // I'm hijacking the params keyword here for splatting
User splatAClass = CreateUser(params user);
On the programmer's side, there would thus no need to think about whether to construct a tuple or an anonymous type. On the compiler side, it would need to choose whether to use anonymous type, Tuple<> or ValueTuple<>, depending on how the type is used
I'm assuming that decomposition and splatting matches by name (though the case of the first letter gets changed)
@huan086
The problem is that the Tally
methods in your example need to have a type embedded in their signature that can be recognized and consumed by the CLR and other languages. What exactly is (int sum, int count)
? Given that the CLR doesn't have type equivalence the compiler would have to emit some form of type to represent the tuple but that would prevent the compiler from automatically treating the other types as the tuple type.
The issue with deconstruction is that with normal CLR types there is no inherent order to the properties or fields, so you'd be forced to always deconstruct by name. Given that the naming convention for public members differs from that of local variables or parameters that would also largely preclude deconstruction automatically by name.
public (int sum, int count) Tally(IEnumerable<int> values) { ... }
to me is the same as public (and hence not so) anonymous types, which have been requested since the local anonymous type feature was rolled out. As many others, I'm not sold on their use, so I guess the feature is not worth the effort.
But there is definitely a need to better handle multiple output-parameters, and as such the feature could have its merits. The representation above would definitely be better readable than that hog:
public void Tally(IEnumerable<int> values, ref int sum, ref int count) { ... }
Disregarding, if one or the other declaration is used, to be better usable at the consumer side, calls to such functions should be allowed as follows:
var x = Tally(...);
Console.Write(x.sum);
Console.Write(x.count);
The type of x is a type that completely vanishes at compilation, the code can be transformed into a simple variable-list. The compiled code should look like this:
int x_sum;
int x_count;
Tally(..., x_sum, x_count);
Console.Write(x_sum);
Console.Write(x_count);
No new inaccessible complex type gets introduced; other systems can still use the hog-signature with its primitives.
Aside from this particular feature, I tend to think, new language features should make life easier. So, as I think about this from time to time, why can't we finally make C# (and VB) method result declarations typeless by default, inferring their type from the return statement?
public Tally(IEnumerable<int> values) { return new {sum = values.Sum(), count = values.Count()} }
If there is no return type, it is void. This code would be easier to refactor.
@McZosch, check out the answer @gafter gave me here and you will probably understand why they can't/won't infer types from the return statement itself and why types are actually important to some scenarios.
Now, I have _bad_ solutions for it like having rules around it but then the code becomes inconsistent and so I don't have any _good_ solutions to this problem.
So you might need to think about a really compelling argument for them to consider it.
This sounds great! Will this be a struct or a class? I hope the former.
@drbo this proposal states that tuples will be value types as you desire.
I've heard some people say that if we don't need to define specific return values/classes in some cases value can be lost, and specific typing so you could know where data came from.
One possible way around that is to autogenerate types based on the class and method name.
Another possible option is just making the return values like normal types defined inline as an option:
public TallyResult (int sum, int count) Tally(IEnumerable
So this way a type is created called TallyResult (that we optionally provided).
It might translate to:
class TallyResult
{
int sum;
int count;
};
or struct if struct keyword used.
An example use could be:
TallyResult results=Tally(values);
Benefits:
Concerns:
@wxs1 auto-generating types for this proposal is a really, really bad idea in terms of management.
I don't think people wants to see the compiler spit out code automatically and see it in their projects, not to mention, that it means people can use these types and then when you change the return signature it will spit out a new struct or overwrite existing file which can break the code.
I don't think people wants to have a complex workflow because this creates yet another obstacle and for large projects it adds yet more files for the IDE to parse and deal with which can make large projects even more challenging and I doubt people want unnecessary challenges.
Another thing, the compilation takes place and then it generates files, this can be a problem because what if you have a type with some name that matches the name of the generated type?
If the compiler auto-generate code it's better that it will be contained in the assembly behind the scene not as part of the project.
The compiler shouldn't be responsible for creating classes/structs easier but the IDE, it's out of context.
This proposal is about expressing tuples in the languages and thus provide a better way to deal with multiple return values.
@eyalsk so the second part I mentioned could still work by making a named tuple.
public TallyResult (int sum, int count) Tally(IEnumerable values) { }
Translates TallyResult to an equivalent of Tuple
An example use could be:
TallyResult results=Tally(values);
As mentioned some benefits/detriments with the result. Not sure if that would turn out to be a compiler issue as well though.
I like it because it can avoid the use of var, since var can sometimes mask inappropriate changes in type.
But not sure I like the idea of defining a named tuple in line, but it might make things cleaner.
@wxs1 yeah but var is not part of the proposal, I think it was initially on the table or just an idea but they dropped it.
If you refer to the to the actual places where var is used above, this is optional, as far as I understand.
So tuples seem to be simply anonymous record types....
edit: reminder to self: finish coffee before starting to type my thoughts out.
@bbarry Per the proposal as it stands no new types would be generated. Rather the BCL will have a new set of value-type tuples added and this feature would use those:
Given:
public (int sum, int count) Tally(IEnumerable<int> values) { ... }
Would produce:
[TupleNames("sum", "count")]
public ValueTuple<int, int> Tally(IEnumerable<int> values) { ... }
That way you could pass the tuple around to anything also expecting a tuple of the structure (int, int)
. The attribute would exist to give the compiler a hint as to whether the items have names so that you could write the following:
var tuple = Tally(values);
var sum = tuple.sum; // compiles to tally.Item1
@wxs1 I have to say I really like your proposal of inline declaration of named types.
I think the dilemma is becoming clear. On the one hand, many users seem to want named tuples and/or the safety of explicitly declared classes/value types (for maintenance reasons).
On the other hand, there is also some kind of consensus that generation of new types is not desirable, and furthermore structural comparison of anonymous type declarations would be useful to avoid generating types with the exact same signature (for productivity reasons).
At first glance, these two requirements seem at direct odds with each other.
I like your proposal because it addresses cleanly one major sticking point that could bring together both sides of this debate. Specifically, even though everyone seems to like explicitly declared types for returning multiple values, these types are a pain in the a** to create.
There are simply too many little things to get right (implement constructors, properties, IEquatable, hash codes, operator overloading, etc, etc) that keep getting repeated again and again, with a large probability of mistakes.
I think the language could be dramatically improved if there was an agreed upon way to create these "helper" value/reference types and the compiler provided syntactic sugar to very easily create and unify over these types. This has the potential to make it almost as easy to create proper return types as it is to declare anonymous types in other dynamic languages (e.g. python), as you nicely demonstrated.
I buy this solution! It seems doable, it doesn't require changes to the CLR, it's elegant, clean and seems to have all the maintenance/productivity tradeoffs in the right place.
@glopesdev auto-generating types is not something the compiler should do unless it's part of the output to support language features.
The issue is about being able to 1) express multiple return values 2) make it easier to maintain the code.
If you need to implement IEquatable, hash code or/and override operators it means that the object should be part of your domain and you shouldn't use a tuple for it.
This proposal doesn't need to change anything in the CLR, it's elegant, clean and all these goodies so not sure what you're saying. :)
@eyalsk I agree with you, but some users seemed to raise issue with Refactoring. If I refactor a named parameter, and this was part of a tuple type unified across multiple places of the assembly, do I want to refactor at all these places? Is there a way to cleanly make these inferences if you let the type escape the scope of a local function call?
What I meant is that the convenience of structural comparison (i.e. unification) seems to be directly at odds with refactoring (maintenance).
@glopesdev I don't know what they ended up doing or how they are going to deal with it so I can't answer that because it's unclear how they are going to handle a tuple across multiple assemblies yet.
I'm still wondering that it could be good allowing the user to define a name for a tuple/type optionally at least allows more context around the returned data. Developers would be fine with those Tuple/names showing up in their project because they gave them the name.
public TallyResult (int sum, int count) Tally(IEnumerable values) { }
Translates TallyResult name to an equivalent of specific Tuple/type.
The use is really clean:
TallyResult results=Tally(values);
It avoids something like this:
(int,int) tallyResult=Tally(values);
If tally result got passed around or we made a List or dictionary of them it would be clear what that data is exactly.
List<Tuple<int,int>> tallyResults when looking at it in the debugger would probably be a little mysterious as to what the tuple values were.
If instead that were List<TallyResult> tallyResults = ... the compiler could have the name of the tuple/type so we can use it cleanly elsewhere, and see properly values in the debugger, etc.
A side issue/benefit:
Having a specific type might be beneficial to have tuples not automatically splatted for certain return values. Allowing conversion of any object with the same number of values and same types splat into another object of the same number of values with the same types doesn't always make sense and may not be desired.
Example:
public (int sum, int count) Tally(IEnumerable values) { }
public (int sum, int count) Matrix(IEnumerable values) { }
(int,int) tallyResult=Tally(values);
(int,int) matrixResult; //I presume this would be allowed
if (complex expression here)
tallyResult=Matrix(values); //OOOps assigned matrix result to the wrong variable but it compiled BUG
Instead with named Tuple/types:
public TallyResult (int sum, int count) Tally(IEnumerable values) { }
public MatrixResult (int sum, int count) Matrix(IEnumerable values) { }
TallyResult tallyResult=Tally(values);
MatrixResult matrixResult; //I presume this would be allowed
if (complex expression here)
tallyResult=Matrix(values); //OOOps assigned matrix result to the wrong variable BUT in this case the compiler could catch the problem and fail to compile if we don't allow splatting in a named type case which is much safer (could make this optional to optionally allow splatting with an attribute, but that probably shouldn't be the default).
@wxs1 That equivalence is considered desirable. (int sum, int count)
should be assignable directly to (int, int)
as well as (int sum, int count)
and possibly also (int count, int sum)
(where the compiler may reorder the members). It doesn't matter what methods (in what assemblies) returned those tuples.
Allowing the user to define a name for a tuple is like mixing record types with tuples. When your items have a name it's not a tuple anymore, it's a record! Because the names will be erased at runtime this just complicates things in compiler implementation and for developer IMO. F# allows names for tuple items if and only if they are defined within discriminated unions. But since syntax for records and ADTs is the same, that's not applicable in C# and of course, it doesn't make sense.
How is a tuple anything more than a record type that has no names beyond the local scope?
@alrz You might be right that its more like record types. Perhaps that might be nicer than Tuples then if tuples are holding us back from having the ability to define easy safe return types.
@HaloFour As my example shows there are times when you might not desire that equivalence for safety. One of the great benefits of C# has been its type safety, preventing you from getting too loose with your objects. The addition of var I think changed that a little, walking a little bit of a tightrope.
I'm just not sure if we want to open things up to always allow all return or parameter values to splat anywhere just because the types match. If you think about a class that has multiple methods and different parameter types, at least the method itself has a name so if you call the method with a set of parameters you know only that method will be called with matching parameter types.
But with splatting since it can splat to any matching return bucket with matching types, it opens up these variables to be written with values that match in type but don't make sense. Yes there are times when splatting can be nice. I'm just wondering if that should be the default we all want to see in C#. Is the risk worth the reward. Can we make both scenario's available, perhaps with the ability to name these things we can.
I like how Swift offers optional naming for tuples. It makes the consuming code nicer to read (who wants to see .0
or Item1
?) and can help avoid bugs accidentally referencing the members in the wrong order. But they're also entirely optional and a named tuple is interchangeable with an unnamed tuple of the same structure.
var tuple1 = ("Bob", 48)
// to named tuple
var tuple2 : (name: String, age: Int) = tuple1
// back to unnamed tuple
var tuple3 : (String, Int) = tuple2
// reordered members by name
var tuple4 : (age: Int, name : String) = tuple2
var tuple5 : (Int, String) = tuple2 // error
var tuple6 : (name: String, foo: Int) = tuple2 // error
I'd be happy if C# achieved the same degree of functionality with tuples.
@wxs1 And if you don't desire that equivalence you can define a type separately. Coming up with a middle-ground syntax between tuples and records/structs/etc. seems unnecessary.
@HaloFour That is confusing, it's not clear that you are deconstructing the tuples or defining the tuple types. Of course you are defining the tuple types but it looks like that you are just deconstructing the tuples. weird.
And hey, I think someone said "local scope". If not, so they are kind of anonymous types? Because in that case compiler must generate specific tuple types for each variation. weird it is.
@alrz Well that's just Swift's notation for defining the type of a variable. If you wanted to deconstruct you'd put the identifiers in parenthesis. But we're not here to debate Swift's syntax. :smile:
var (name, age) = tuple2
@HaloFour Yes, we're debating your happiness regarding same degree of functionality with tuples in C#.
@alrz Which has nothing to do with messed up Swift syntax is.
@HaloFour so, you're saying that C# syntax should be messed up in its own way?
@alrz Not sure how useful this string of one-liners is to this discussion, but obviously, yes, C# should have it's own syntax.
Names were requested by others and are a part of the proposal as a result. Swift has similar functionality with its own syntax (for better or worse). I find it friendly to use despite never having coded anything in Swift outside of a few LOC here and there in the xcode Playground.
@HaloFour These are not one-liners this is the new kind of discussion which I just invented. like named tuples items which would be erased at runtime like Java generics and only accessible in local scope like anonymous types and I have no idea what is going on here. "Requested by others" no offence but "others" requested "Allow multiple return values in C#" without mentioning a thing about tuples. what does that even mean. this "others" led us to an else
clause following a let
which basically is a variable declaration syntax. "C# should have it's own syntax." Yes, but this is not just syntax. I have no idea how could this be implemented. local scope? ok.
@wxs1
tuples are holding us back from having the ability to define easy safe return types.
How?
@alrz
Multiple returns led to tuples, tuples led to naming, naming led to type equivalence, type equivalence led to common structs with "erasures" and that led us to this proposal. Sorry that you joined in the middle.
I'm not terribly thrilled with the whole "erasure" bit either. For locals it's not very relevant, the compiler could emit raw local slots in place of the tuple all it wanted and nobody should care as long as it behaved as expected. For anything exposed there will mostly likely be metadata in the form of attributes in order to provide hints to the compiler, so the names are "erased" from the type but not completely. Supporting compilers will understand a named tuple exposed by a method in a referenced assembly just fine. To be honest I'd kind of prefer that unnamed tuples use common generic structs and named tuples use automatically generated types that perhaps includes an implicit conversion operator to the structurally-equivalent common generic struct.
I'm sorry that you disagree with the direction that some of these proposals are going. I'm not getting everything I wanted either.
@HaloFour I wonder why when tuples led to naming, naming didn't led to records. I hereby request anonymous records, I have no idea what it would be. Thanks for clarification on the implementation by the way. "I'm not getting everything I wanted either." you're saying that I want to get everything that I wanted? I wouldn't for sure.
@alrz
And hey, I think someone said "local scope". If not, so they are kind of anonymous types? Because in that case compiler must generate specific tuple types for each variation. weird it is.
I said this.
Outside of whatever method body they are used in, it seems important to be able to have a method like:
public (int sum, int count) Tally(IEnumerable<int> values) { ... }
which may or may not be in the class you are working on or the file you are in.
When this happens I think it would be nice if the compiled signature was something like this:
[Tuple("(int sum, int count)")]
public ValueTuple<int,int> Tally(IEnumerable<int> values) { ... }
where ValueTuple
is:
namespace System.Tuples {
public struct ValueTuple<T1, T2>(T1 Item1, T2 Item2);
public struct ValueTuple<T1, T2, T3>(T1 Item1, T2 Item2, T3 Item3);
public struct ValueTuple<T1, T2, T3, T4>(T1 Item1, T2 Item2, T3 Item3, T4 Item4);
// maybe more?
}
The compiler could treat types with the name ValueTuple
in this namespace special in the following ways:
_tuple-type-identifier_:
(
_tuple-component_)
(
_named-tuple-component_)
_tuple-component_:
_type_,
_type_
_type_,
_tuple-component__named-tuple-component_:
_type_ _property-identifier_,
_type_ _property-identifier_
_type_ _property-identifier_,
_named-tuple-component_
edit: 11/6/2015 6pm EST: A tuple with only one value is probably ambiguous in a number of places; getting rid of it
Perhaps we should restart with determining the use case for tuples. We already have anonymous classes, we are likely to get record-like classes and structs with value type semantics, and here we are, asking for value tuples with or without named items.
Why and when would I even use a tuple? One case is multiple returns from a function, like Tally
or MinMax
. Another is enriching an existing set of objects with some data; in the "area calculation" pattern matching example an anonymous class is used to assign an area to each object, but a ValueTuple<Figure, double>
would work just as well.
Can we theoretically have tuples with named items? Why not, function arguments look like pseudo-tuples already, and we can refer to specific arguments by name there. The type of Foo(int bar, int baz)
is still a nameless Action<int, int>
, though, so the type of (int bar, in baz) Foo()
will be a Func<ValueTuple<int, int>>
as well. We could even use the names to destructure the result, e.g. var qux:baz = Foo();
, but the names can be a part of the function, not the tuple itself.
Do we really need named tuples? It would be nice to be able to say foreach (var figure in figuresWithAreas) {WriteLine($"The area of {figure.itself} is {figure.area}");}
, but I don't think having named tuples as public members or return types is a good idea. If the names are really so important for the understanding of the tuple, a record type would work much better.
Or perhaps we can unify the idea of tuples, tuples with named parameters and named tuples with named parameters, i.e., records?
@bbarry So basically, you are coupling the type definition (named tuples) to the context (method declaration)? If I define a local variable as a named tuple or use it in typeof
where your attributes would go? Or the compiler should treat them differently?
@orthoxerox For the use case you've mentioned foreach (var figure in figuresWithAreas) {WriteLine($"The area of {figure.itself} is {figure.area}");}
I would suggest #6067 syntax where you can deconstruct the foreach
variable right where it defined foreach (case (var itself, var area) in figuresWithAreas)
.
If you decide to name the members of the tuple, those names are valid within that method in that order.
If I wrote this method:
public (int, int) Tally(IEnumerable<int> input)
{
int sum = 0, count = 0;
foreach (var i in input)
{
sum += i;
count++;
}
return (sum, count);
}
I haven't specified the names for the tuple at all (and that is fine). I could put names on that signature:
public (int sum, int count) Tally(IEnumerable<int> input)
And those names would be visible when someone with intellisense looked at the method or at generated documentation that was aware of the tuple functionality.
If I wanted to call it:
(int sum, int count) result = Tally(Enumerable.Range(1,10));
Now I have declared the type in such a way that I can access the properties:
Console.WriteLine($"{result.sum}, {result.count}");
If I had done this inside Tally:
public (int sum, int count) Tally(IEnumerable<int> input)
{
int sum = 0, count = 0;
foreach (var i in input)
{
sum += i;
count++;
}
var result = (sum, count);
Console.WriteLine($"{result.sum}, {result.count}");
return result;
}
There are errors there if the following changes might be made:
var result
var keyword is replaced with a tuple type identifier that is not either (int, int)
or (int sum, int count)
If the type names were removed from the method signature and replaced in the var
and the writeline were updated, that would be ok:
public (int, int) Tally(IEnumerable<int> input)
{
int sum = 0, count = 0;
foreach (var i in input)
{
sum += i;
count++;
}
(int sumothername, int count) result = (sum, count);
Console.WriteLine($"{result.sumothername}, {result.count}");
return result;
}
@orthoxerox, @alrz
For the use case you've mentioned
foreach (var figure in figuresWithAreas) {WriteLine($"The area of {figure.itself} is {figure.area}");}
I would suggest ...
I think this syntax should be sufficient:
foreach ((Figure itself, double area) figure in figuresWithAreas) {
WriteLine($"The area of {figure.itself} is {figure.area}");
}
or simply using var
if the type ValueTuple<Figure, double>
is identified with names elsewhere in the method.
I am not sure if this should be an error:
foreach (var figure in figuresWithAreas) {
WriteLine($"The area of {figure.Item1} is {figure.Item2}");
}
If the tuple goes without being named, I think that should be acceptable. Is it a good idea to allow indexed access as well?
@bbarry Re "those names are valid within that method" "those names would be visible when someone with intellisense looked at the method" 1. Which? 2. You're saying that this is kind of tooling support in the language itself?
foreach ((Figure itself, double area) figure in figuresWithAreas) { WriteLine($"The area of {figure.itself} is {figure.area}"); }
So, this would be possible as well?
list.Select((int a,int b) x => ...)
Note that if I missed the x
the signature of the lambda changes! Is pattern deconstruction integrated to the type definition here?
those names are valid within that method
as a matter of syntax/semantics
those names would be visible when someone with intellisense looked at the method
as a matter of tooling
list.Select((int a,int b) x => ...)
I don't see why not? That is not deconstruction. It is simply naming the properties of the type so they are individually accessible inside the lambda as x.a
and x.b
.
edit: If splatting happens:
list.Select((int a,int b) => ...)
(note the missing x
); that is either splatting some IEnumerable<ValueTuple<int, int>>
or that is an IEnumerable<int>
list and calling the Select
extension method that passes the index in.
@alrz
Re "those names are valid within that method" "those names would be visible when someone with intellisense looked at the method" 1. Which? 2. You're saying that this is kind of tooling support in the language itself?
For named tuples used within a method the compiler/IDE would understand those names, but the names would be completely erased at run time. For named tuples exposed outside of the method as parameters, return types, etc., attributes would most likely be used. The exact implementation details of those attributes has not been specified.
list.Select((int a,int b) x => ...)
This is already valid, as long as list
is an IEnumerable<int>
since Enumerable.Select
has an overload than accepts Func<T, int, TR>
. Oh, you wanted automatic deconstruction? No. That would be ambiguous with lambda parameter list syntax.
I missed the parentheses, it should be list.Select(((int a, int b) x) => ...)
because it's using _explicit-anonymous-function-signature_. Phew.
they are individually accessible inside the lambda as
x.a
andx.b
.
It seems that a
and b
are directly accessible in the body, that is so damn confusing. I can't look at this list.Select(((int a, int b) x) => ...)
without having a heart attack.
@HaloFour I think I'd better to look forward for further details, I'm just hurting myself.
@alrz
I would suggest #6067 syntax where you can deconstruct the
foreach
variable
We have no plans to do decomposition with foreach.
So, this would be possible as well?
list.Select((int a,int b) x => ...)
Note that if I missed the x the signature of the lambda changes! Is pattern deconstruction integrated to the type definition here?
We have no plans to do pattern decomposition with lambdas. I apologize if we've given the mistaken impression that any of these constructs have anything to do with decomposition or deconstruction.
@gafter If (int a, int b)
is a valid _type_ then list.Select(((int a, int b) x) => ...)
would be possible. But a
and b
wouldn't be directly accessible, because they are part of the tuple type declaration, right?
@alrz Right, in that case a
and b
are members of x
and can be accessed using x.a
and x.b
.
@wxs1 The desire for named result type declarations misses the point of tuples. They represent aggregates of fields that have no inherent relationship to one another. They are only related by the structure of the operation which composes them.
As @MadsTorgersen said when opening this proposal:
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.
It's not simply about the work involved in defining the transport type and giving it the proper implementations of Equals, GetHashCode, and so on; it is also about how the outputs of the method are more cleanly expressed as an ad hoc association of optionally named values.
Consider this example:
``` C#
public static IEnumerable
source.Select((element, index) => new Indexed
...
public struct Indexed
{
public Indexed(T element, int index) { Element = element; Index = index; }
public T Element { get; }
public int Index { get; }
...
}
This method is useful when you need to incorporate the original index of an element in operations after the sequence has been further filtered or shuffled. The type `Indexed<T>` is meaningless. Its existence pollutes the containing namespace with constructs that have no value and are not meant for use except via the `WithIndices` method.
Now consider:
``` C#
public static IEnumerable<(T element, int index)> WithIndices<T>(this IEnumerable<T> source) =>
source.Select((element, index) => (element, index));
The declaration is more meaningful and so is the consuming code. Additionally having the results splat into compatible targets is ideal here because you don't have to refer to the elements of the resulting sequence by the name indexed.Element
.
At any rate, if you want semantically named, independently meaningful and type incompatible result types you should declare them. Perhaps records will make this process easier, but that is not what tuples are about.
@aluanhaddad Exactly, your example could use a record type public struct Indexed<T>(T Element, int Index);
. IMO the former is more readable than the latter. Tuples, by definition, are just an ordered set of data. That's it. So, actually, named tuples would be kind of anonymous records? (with type equivalency and erasure?) I think this makes code worse rather than better, and the fact that the metadata is bound to the context (method attributes) would cause to make it even worse.
I've done some prototyping to see if "named tuples are anonymous record structs" is a feasible approach.
First of all, I had to get rid of name-based equality: (foo:1, bar:2) == (bar:2, foo:1)
. This required reflection or a weird dictionary-like interface to support, so I ditched it.
Second, I had to get rid of cross-assembly name-and-position-based equality, since it required a similar interface. This, of course, destroyed record and named tuple equality as well: (foo:1, bar:2) == new Qux(1, 2)
, where there's a named struct Qux(int foo, int bar);
.
The remaining functionality (named tuples can exist, but cannot be used anywhere in public members; both named tuples and records can be equated with nameless tuples, but not with each other) might be useful, but feels kinda limited. With record structs being literally a one-liner, I don't feel they are that necessary. The only reasons named tuples might be better than existing anonymous classes is value type semantics and being able to pass them to private and internal methods.
Named tuples in public apis of 3rd party assemblies will require considerable heavy lifting by the compiler or work by the author to consume. If
(int foo, int bar) M1()
(int bar, int foo) M2()
var m1 = M1(); var m2 = M2(); if(m1 == m2) { Assert(m1.foo == m2.foo && m1.bar == m2.bar); }
For equality checking this type that represents such a thing must be more complex than a simple struct with 2 int properties. That means a cost is being paid for carrying around the names. I don't think that is valuable enough to justify on a struct datatype. This cost would be on nameless tuples as well.
Thus I think named tuples are of negligible value over record struct types.
That said, naming the contents of a tuple within a method (or perhaps even within members of a class) in a similar way to how anonymous types currently work from the user's perspective does seem valuable. I've put my prototyping examples in a gist so others can see: https://gist.github.com/bbarry/35e44d7a88a66be34280
@bbarry We do not plan to override operator ==
, but even if we did it would be positional and ignore member names. Our current plan is to implement a tuple type as a simple struct with two typed fields, sort of a value-typed analog to the current reference-typed tuples.
struct ValueTuple<T1, T2> : ITuple2
{
public T1 Item1;
public T2 Item2;
ValueTuple(T1 item1, T2 item2)
{
this.Item1 = item1;
this.Item2 = item2;
}
object ITuple2.Item1 => Item1;
object ITuple2.Item2 => Item2;
// also GetHashCode, Equals, and ToString
}
interface ITuple2 // used for pattern-matching when the static type is not known
{
object Item1 { get; }
object Item2 { get; }
}
The member names you see at use sites such as (int x, int y) t
can only be used at compile-time for
t.x
if (t is (x: int x, y: int y)) ...
(x: 3, y: 4)
but in all three cases the names are only part of the type at compile-time but erased at runtime. Two tuple types with corresponding element types but different member names will be considered "compatible" for various purposes.
var x = (a: 3, b: 4);
(int z, int w) y = x; // y.z == x.a and y.w == x.b
if (x.Equals(y)) // this is true
class Base { public virtual (int x, int y) M() {...} }
class Derived: Base { public override (int a, int b) M() {...} } // ok
That is almost precisely what I was assuming would be done if this proposal went through. I am not certain this is necessary but it seems nice to have:
var x = (a: 3, b: 4);
(int z, int w) y = x; // y.z == x.a and y.w == x.b
(I'd be happy with z.a
and z.b
forced on to me.)
I was also unsure of mutability being a good idea.
@gafter why not override ==
and implement IEquatable<ValueTuple<T1, T2>>
?
Will destructuring assignment work for tuples?
Unnamed tuples:
int x, y;
var t = (1, 2);
(x, y) = t; //x==1, y==2
var (z, w) = t; //z==1, w==2
var (*, v) = t; //v==2
And named tuples:
int x, y;
var t = (foo:1, bar:2);
(x:bar, y:foo) = t; //x==1, y==2
var (z:bar, *) = t; //z==2
@orthoxerox Yes, probably operator ==
and IEquatable<ValueTuple<T1, T2>>
too.
@orthoxerox @bbarry For decomposition, see #6400. It is too early to say whether or not there will be other forms of decomposition.
@aluanhaddad Exactly, your example could use a record type public struct Indexed
(T Element, int Index);. IMO the former is more readable than the latter. Tuples, by definition, are just an ordered set of data. That's it. So, actually, named tuples would be kind of anonymous records? (with type equivalency and erasure?) I think this makes code worse rather than better, and the fact that the metadata is bound to the context (method attributes) would cause to make it even worse.
@alrz My point was that I do not want the type declaration at all. Records would be very useful, but in this particular scenario, I would like to avoid an irrelevant type, _with_ a public constructor, cluttering the namespace when it is only used to associate incidentally related values by a single method. Anyway, it is an example of where I think tuples would be useful.
@gafter
Is it possible for implicit renames to cause compile-time errors?
Because your example undermines the value of having a static type system:
var x = (a: 3, b: 4);
(int z, int w) y = x; // y.z == x.a and y.w == x.b
if (x.Equals(y)) // this is true
That second line is dangerous because it's easy to get the order of properties wrong, and this is the only place in the language where reordering named properties implicitly changes their meaning.
For example, it's very conceivable for a programmer to pack (row, col)
but to unpack (x, y)
, which might cause bugs that are painful to debug: no compile time error, and no runtime error, but semantic corruption instead. Nasty.
Type erasure is still possible in this scenario (though not ideal from a usability perspective); after all that just means that if you manage to lose static type info (cast to object and then to another tuple type, say) you'll get results that are confusing. And even that can be prevented: there's no reason I can think of to allow casting to a tuple type _at all_, so then the hole in the type system is limited to tuples that are cast to object and compared (e.g. using Equals
).
That second line is dangerous because it's easy to get the order of properties wrong, and this is the only place in the language where reordering named properties implicitly changes their meaning.
@EamonNerbonne I believe function calls suffer from exactly the same issue, no?
Refactor Tally(int row, int col)
into Tally(int x, int y)
and you also have a silent compile-time, runtime, semantic break.
It looks like named tuples will follow exactly the same behavior as function argument lists, with the exception that named parameters are accessible through reflection, whereas tuple named parameters will be syntactic sugar only.
@glopesdev there are similarities to function arguments, but there are also differences. You cannot access method (or lambda) arguments as if they were properties of an object. The syntax of object access and argument access has always been considerably different, nobody could previously have confused the two. If you write obj.x
you would never expect to silently actually access obj.y
in C#6 - all property access is by name. Also, arguments are always syntactically scoped. When you access an argument x
, you know that the argument declaration will be in the same file and easy to find - it's going to be at the top of enclosing scopes - so the order of arguments is never implicit and always nearby (especially if you follow normal best practice and keep methods small). Unfortunately, method calls are positional, and at the callsite the argument list declaration is likely also not nearby. But here too, even though arguments are positional, _if_ you do call a method using named arguments, it is a compile time error to mis-name an argument. If you call SinkShip(row: 3, col: 4)
it is a compile-time error given the method definition void SinkShip(int x, int y)
.
In this new proposal, it's much easier to make mistakes. The declaration of the tuple ordering will _not_ be in scope; it may well be in some entirely different file - so it's much easier to misremember and reorder variables by accident - in fact, it may well become _common_ to rename variables in some contexts if the "original" name lacks some context in its new location (e.g. (int shipX, int shipY)
where most of the code isn't dealing with ships as opposed to (int x, int y)
inside code that primarily deals with ships). And when you _do_ refactor, behavior is going to be surprisingly different in var
vs. explicitly typed tuple scenarios. If you think all your consumers use var
(which is a common enough policy currently in C#), it is a correct refactoring to reorder (x,y) into (y,x), since everyone accesses tuple members by name anyhow. But if just one of those usages uses explicit members, the refactoring will still compile and run, but be semantically incorrect.
Finally, the fact that the design of function calls has this particular pitfall doesn't mean a new feature should too. structs
don't have this pitfall, and why not follow that example instead? In most places in C#, resolution is by-name, not by position. Classes, methods, properties, fields - everything is named. Even in method calls, although names are optional, when names _are_ provided, they take precedence over the position.
Making tuples primarily positional just invites lots or really nasty bugs, with little to no upside. The only downside I can think of is that it makes repacking a tuple to rename its members a little more verbose - but I'm not sure that's a bad thing, since that sounds like an operation I wouldn't mind standing out in code.
I understand that there is a certain symmetry between tuples and method arguments, but I don't think there's any benefit (can you think of anything specific?) in retaining (and in some ways making worse) this one quirk of method arguments in tuples.
TL;DR;:
I can imagine named tuples would come in handy with #5445 to pass multiple arguments in a postfix function application. Would it be possible, @gafter?
@EamonNerbonne I expect it is possible for us to catch the most problematic cases of tuple renaming. We'll know more once a prototype is further along. Currently you can reorder the names of the parameters in an overriding method; is that an excuse to allow renaming the names of the tuple results? Or should we only disallow renamings that reuse old names in new positions?
@alrz possibly if we also add "splatting', but it seems unlikely.
@gafter is parameter renaming necessary at all? Let's say (1, 2)
and (foo:1, bar:2)
are equatable and mutually assignable, (1, 2)
and (baz:1, qux:2)
are as well, but (baz:1, qux:2)
and (foo:1, bar:2)
are not. If you want to turn one into the other, you'll have to explicitly deconstruct and reconstruct your tuples.
@orthoxerox Do tuple conversions (e.g. betwixt(int, int)
to and from (int x, int y)
) have to be identity conversions? Identity conversions are transitive. What breaks if we make identity conversions no longer be transitive? Does type inference depend on that, for example? These are good questions and we're still investigating.
@gafter they can be implicit casts. If both sorts of tuples are value types, there shouldn't be any issues, since the values are copied anyway, or should it?
@orthoxerox "casts" are not a kind of conversion. Identity conversions do not necessarily involve copies. Generally speaking, implicit conversions are transitive except when user-defined conversions are involved.
@gafter please bear with me, I do not write compilers for a living. What I meant was that value types do not an intrinsic representation of their type, I think it's impossible to use castclass
on them. So every time we want to treat a named tuple like an unnamed tuple we must call a conversion function first. If they have the same memory layout it _might_ get optimized away.
I think I get what you mean, though. Neither tuple is a subtype of another, so if we treat this relationship as "an unnamed tuple _is also_ a named tuple and vice versa", it looks strange that two named tuple value can be also the same unnamed tuple value, but are otherwise incompatible.
I might be influenced by my PoC, where named tuples are structs with implicit user-defined conversions to and from ValueTuple
, so there's no "is also" relationship, there's "the value can be automatically converted to" instead.
I've just read all of the comments for this issue, and it seems that the original starting point has been lost...this isn't really about tuples.
What is desired is to make multiple return values work better for the programmer. Multiple return has three parts, returning multiple values, for which we have various methods that work well enough, assigning multiple values, and clearly communicating that multiple values are being returned.
These are related, but distinct features.
1. Returning multiple results. Out params and objects (whether tuples or regular class/structs)
works well enough and doesn't really need any adjustment.
2. Assigning multiple values. This currently doesn't exist. Either a single value is returned which
contains subparts which may or may not be set, or the values are set as a side effect of the
function call.
3. Communicating that multiple values are being returned/consumed. This isn't a matter of
functionality, but of clear syntax. The current syntax is at best slightly obscure, and at worst
misleading.
Skipping (1) as working, limiting (2) to tuples would be unnecessarily restrictive. Of the suggested syntaxes for assigning multiple values at the same time, I find the arguments for symmetry with method arguments most convincing. It seems to me that:
(int count, int sum) = Tally();
Should take the first two properies of whatever object is returned by Tally and assign them to the newly created variables count and sum. This makes the question of what gets returned moot -- it works on tuples, classes, structs and object initializers.
(count, int sum:Item2) = Tally();
Would assign the first property of the result of Tally to the existing variable count, create a variable sum and assign it the value of the Item2 property. Whatever object is returned must have a Item2 property (either a property with that name or a propery with a PropertyName attribute with that value).
(var count, var sum) = {1,2};
Would assign 1 to count and 2 to sum.
This has the obvious drawback of seemIngly introducing a "default" property...
ClassTrip ct = new ClassTrip{Teachers=2, Students=23, Destination="Vegas"};
(int teachers) = ct;
But it really only makes it easier, you could do the same with reflection (absent the compile time guarantee that it has at least one property).
It has the advantage that it also works on object properties
Person p = new Person();
(p.FirstName, p.LastNam) = GetFirstLastName();
I don't know how difficult it would be to to turn:
int x;
bool parsed = Int32.TryParse(s, out x);
Into
(bool parsed, int x) = Int32.TryParse(s);
(Or rather the reverse), but that would allow making the clearest instance of multiple return values, explicit.
The major drawback I see from this approach is that it doesn't allow passing the results directly into a method, unless the method takes the underlying types. So that ReturnCountAndSum cannot be passed to DivideSumByCount. I don't see why that should be a deal breaker.
Fixing (2) this way, would make it clear that you were handling multiple values at the calling site, but would still leave ambiguity with the method signature, i.e. (3) is not completely resolved.
But if (2) is handled as above, (3) becomes easier -- questions of cross assembly equivalence, and assignment become moot. Return whatever type you want. Anonymous classes could finally be returned, anonymous structs could be added, or tuples created or something else entirely. Whatever is chosen would be divorced from the assignment issue.
The question of what to return and the syntax for creating it doesn't go away, but at least it is divorced from how to turn it into a series of values.
I don't have an opinion on the type, or the syntax for creating/returning it. I definitely see value in adding some sugar that makes it crystal clear that multiple values are being returned, and what those values are. Currently a lot of multiple value functions work by returning classes, but you have to read the code carefully to see what is actually being returned. Is it 3 of 5 properties on a class or all of them?
@jrmoreno1 I share your feeling that this issue should be more about support for assigning multiple values than designing better tuples. However, your comment also highlights a couple of problems that make this really hard to do in C#.
(int sum, int count) = Tally()
doesn't really work unless you assume the result is ordered in some way).(int sum:Sum, int count:Count) = Tally()
if we assume the method somehow defined names for properties in its transport type (as we think it should). You could imagine ideally doing something like (Sum, Count) = Tally()
which would simultaneously access the properties and declare a variable with the same name and type (like in anonymous type construction). However, because of different casing rules this turns out to be unsatisfactory...If these two points would not exist, I agree we should very much prefer worrying about a syntactic sugar solution to multiple assignment rather than the definition of new transport types.
@gafter (on the topic of overriding methods with different parameter names being an excuse for allowing tuple member renaming)
Sure, it's an excuse. But why do we need one? Why not simply disallow it, and have tuple work the way the rest of the language does: names over positions?
The consistency argument seems really weak to me. Consistency isn't some intrinsic good, it's a good because... it makes the language easier to learn? It's easier to implement? etc. And C# is already inconsistent in this matter: objects differ from arguments here. There's no intrinsic reason to prefer consistency with the one over the other - if you have symmetry between arguments and a tuple return type you lose symmetry between object and tuple construction. Pick your poison.
For practical benefit, I can't think of any reason whatsoever to use positional tuples, and a strong maintainability reason to prefer names over positions.
@orthoxerox What's the motivation to support a conversion from (int x, int y)
to the name-less (int, int)
at all?
@jrmoreno1
I think you dismiss point 1 too quickly. A shorthand for defining the transport type of the multiple return is itself desirable particularly in those cases where a specific type doesn't make sense as a container for loosely associated values. It's unnecessary boilerplate, especially if you desire the results to be equatable, comparable or usable within dictionaries. This is covered in the background of the proposal above.
Deconstruction of arbitrary types via property patterns is its own proposal (#6400). There has been mention of some kind of syntax to reorder out
parameters as return values as well, although I don't believe I've seen anything fleshed out by the design team.
@orthoxerox The types (int, int)
, (int x, int y)
, and (int sum, int count)
are _precisely the same type_ from the CLR point of view. No code is used to convert among them. Since the member names are not available at runtime, we must to a certain degree treat them as equivalent types at compile-time too. For example,
object o = (x: 1, y: 2);
if (o is (int, int)) // must return true
@gafter
Will the compiler be able to apply conversions between named tuples?
var point1 = (x: 1, y: 2);
(int y, int x) point2 = point1;
Debug.Assert(point1.x == point2.x && point1.y == point2.y);
I assume that the existing is
operator could not be applied to named tuples as the names are unavailable, but what about custom is
operators for pattern matching?
public static class Polar {
public static bool operator is((double x, double y) cartesian, out double R, out double Theta)
{
R = Math.Sqrt(cartesian.x * cartesian.x + cartesian.y * cartesian.y);
Theta = Math.Atan2(cartesian.y, cartesian.x);
return cartesian.x != 0 || cartesian.y != 0;
}
}
var tuple1 = (1.0, 1.0);
var tuple2 = (x: 2.0, y: 2.0);
var tuple3 = (y: 3.0, x: 3.0);
var tuple4 = (foo: 4.0, bar: 4.0);
// legal
if (tuple1 is Polar(*, *)) { }
if (tuple2 is Polar(*, *)) { }
// legal? could the tuple be converted to (double x, double y) by
// reordering the members based on matching up their names?
if (tuple3 is Polar(*, *)) { }
// illegal? the named members are not compatible with the expected named members?
if (tuple4 is Polar(*, *)) { }
@HaloFour The custom operator is
is usually applied after an attempted (runtime) conversion to the type of the first parameter. Therefore the names of tuple elements have no effect on the behavior of operator is
.
It is still an open question what conversions will be statically allowed.
@gafter I was using the operator as an example, I guess the same would apply to any conversion.
Rereading the proposal again I do see the following under the Conversions section:
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.
My opinion is that assignment from named tuple to named tuple shouldn't be permitted if the names aren't the same, just as a precaution. To extend the sample size:
(int count, int sum) rename = Tally(...); // that's probably not what the developer meant to do
(int c, int s) rename = Tally(...); // same problem but with shorthand
However the developer would be free to "convert" to an unnamed tuple and then convert back to a named tuple of arbitrary names and their members would be "assigned" positionally.
@glopesdev Properties are ordered. They are sequential in the source file (although partial classes may confuse that) and they are written to the meta-data in a particular sequence. Reflection returns the same properties in the same order. There isn't a canonical order, but that is just because no one has cared. As for casing, I don't think it would be desirable to declare a variable in that manner, regardless of casing issues. (var name:name) is a bit redundant, but it is the exact same redundancy we have for named arguments.
@HaloFour I don't think I am dismissing (1) too quickly. There are two mechanisms currently used to return multiple values; out/ref and typed structures (aka class or struct). There are an addional two mechanism that are available but undesirable (deepening the stack and registers). A shorter syntax for defining the transport type is (3) clearly communicating multiple values are being returned, not (1) returning multiple values. No one has suggested an alternative mechanism for returning multiple values. Instead they have argued details about what structure to return that will contain those values.
@jrmoreno1 I think that's largely because anything involving CLR changes are very unlikely to actually happen. Given such it seems more appropriate to look at solutions that are reasonably implementable. As you mention C# offers multiple ways to return multiple values which are clearly found lacking due to the existence and interest in this proposal. Moving towards tuples seems to tick all of the right boxes, particularly combined with a syntax that makes it very succinct.
Of course I would be interested in seeing alternatives to tuples from the returning method's point of view if you have some.
@HaloFour as I said in my original post, the mechanism for returning mutiple values works just fine, what is lacking is (a) an easy way to assign the returned values to something else, and (b) a clear syntax indicating that multiple values are being returned.
As of C#6, tuples are probably the clearest way to convey that a method returns multiple values.
I view (a) as the more pressing issue precisely because there is no adequate alternative -- either you use the returned object directly (potentially misleading and frequently with incorrect names), assign the values from the returned object one by one (tedious and space consuming) or use the out/ref syntax (relies upon a side effect, may require an intermediate steps as the actual destination can not be used direct, may not be available at all). Each choice worse than the last.
@jrmoreno1
I view (a) as the more pressing issue precisely because there is no adequate alternative
I'd suggest keeping an eye on #6400:
// assuming use of record or custom is operator
let Tallied(var sum, var count) = Tally(...);
// assuming use of class/struct
let Tallied { Sum is var sum, Count is var count } = Tally(...) else throw null;
// assuming use of tuple
let (var sum, var count) = Tally(...);
either you use the returned object directly (potentially misleading and frequently with incorrect names), assign the values from the returned object one by one (tedious and space consuming) or use the out/ref syntax (relies upon a side effect, may require an intermediate steps as the actual destination can not be used direct, may not be available at all). Each choice worse than the last.
Which is the exact justification of this proposal. The names become mostly irrelevant, you can deconstruct relatively easily into its constituent parts and they are type unified allowing them to be directly reused. It scratches this itch as well as has other benefits.
As of C#6, tuples are probably the clearest way to convey that a method returns multiple values.
And this proposal just builds on that. You could ignore the syntax candy entirely and just use the tuple types directly if you chose to.
@gafter Question, thoughts on tuple deconstruction/patterns supporting both the new value-type tuples as well as existing System.Tuple
? Obviously there are issues since the latter can be null
.
@HaloFour #6400 might be adequate, but it is both overkill and limited. Overkill is obvious, and limited in that the current proposal restricts what can be assigned to. What I described would allow assignment to anything which can be assigned to.
You can almost do what I described with VB's With
With Tally()
Dim sum =.Sum() : Whatever.count=.Count()
End With
The major problem being that if you declare a new variable you have to use it within the With block. The other is that it doesn't offer the possibility of using positional assignment While I know some don't like that idea, it is common and succinct. And speaking of succinct, the VB version isn't... But if the variables declared in the With block weren't confined to the With block, it would almost be adequate. A bit wordy and awkward, but usable.
The VB example illustrates why #6400 is overkill -- all of the properties are known at compile time, you don't need to check types or values, you can just assign the properties.
Thinking about using named tuples in other places, since they are value types rather than reference types, I think LINQ let
should be implemented through tuples instead of anonymous types (in cases that the range variables would pass to the next method — to avoid heap allocation in loops) and also
from employee in db.Employees
select (employee.Name, employee.Family)
should emit tuple item names Name
and Family
(just like anonymous types).
@alrz Those two suggestions are awesome.
Will single tuples (aka singleton / monuple) be allowed?
public (int id) Save<T>(T entity);
@dsaf no. If we did then 1
would mean something different from (1)
, or we'd have to come up with some completely different syntax.
Why would you want that?
(By the way, it's called a "oneple", not to be confused with a Wumpus ;)
@gafter Will it be possible to construct an anonymous named tuple via reflection? I'm thinking specifically about using them in dynamically generated Expression trees.
@glopesdev
"named tuples" are purely a compiler feature. The names don't exist at runtime, except potentially embedded as attributes onto fields, method parameters and return types. If you're trying to construct a tuple of two values at run-time you'd just use ValueTuple<T1, T2>
and have to refer to their values through their properties Item1
and Item2
.
@HaloFour That's right, I forgot about that. More work for dynamic generation, but fair enough, it's preferable that way.
@gafter Personally I was just wondering if it would be possible to abuse it to give names to primitive type return values :). The correpsonding framework type exists though: https://msdn.microsoft.com/en-us/library/dd384265.aspx
@dsaf we are planning to use a new set of struct
tuple types probably callied ValueTuple
.
@gafter regarding a oneple (1,)
is pretty common syntax for single-item tuples.
@Phrohdoh, only if (1,2,)
is a valid syntax for a twople, and so on.
@paulomorgado @Phrohdoh or if it's a named tuple, it doesn't need a trailing comma (foo: 1)
. But still I can't think of any reasonable use cases other than in #5058.
I suggest to use var
as mutable field in tuple and val
as immutable field in tuple.
(var int X, val int Y)
to create tuple with mutable X and immutable Y. By default fields are immutable so it equal to (var int X, int Y)
.
Also val
can be used like var
for immutable variables val x = 10;
val string s = "123";
What is wrong with mut
for explicit mutability?
var
already has its roots elsewhere and I don't understand why people think it should be changed to do more/other things too.
In c# var
already means mutable. And default syntax means mutability so we need syntax for immutability. I suggest val
.
@AlexRadch C# is going to be using let
to define immutable locals per #6400.
Also, this tuple syntax is intended to only be short-hand over using a set of existing BCL structs, potentially named ValueTuple<T1, T2>
, etc. The members of these structs will all be mutable and C# couldn't really do anything to affect that or to impose immutability, at least not where the tuples may escape the bounds of a single method.
I think it is no difference to use let
or val
for immutable values.
let
or val
shout be used in method params also.
double Average<T>(val List<T> source) // can not change source
{
val c = source.Count; // correct
source.Add(5); // compiler error
return (double) source.Sum() / c;
}
class List<T>
{
void Add(T item); // can change self
immutable int Count; // can not change self
}
@AlexRadch For those case I would like if we could reuse const instead
And for enforce collection cannot be modified. It should just cast to ReadonlyCollection or IEnumerable
double Average<T>(const IEnumerable<T> source) // can not change source
{
var c = source.Count; // Incorrect
var c = source.Count(); // correct
source.Add(5); // compiler error because IEnumerable has no Add
return (double) source.Sum() / c;
}
Here discussion not about Average
method!
We have many suggestion how describe immutable variables: val
or let
or readonly
or const
.
@AlexRadch If you open your eye and mind you would see I just copy your example to be my example that YOUR use case is not necessary need let or val or anything new. So you should not make things more complicate by add option to do same thing with new keyword here
You are the one who start using Average here, not me
@AlexRadch If you open your eye and mind you could see that me too said about enforce collection cannot be modified could just use const IEnumerable anywhere, not about Average method in particular
@Thaina @AlexRadch
This proposal is about tuples, not immutable values. See proposal #115 for immutable parameters and locals.
@AlexRadch
In c#
var
already means mutable.
No, it means "please infer the type here". See its use in foreach
, for example.
And using
.
Using parenthesis for tuple literal (1,'A', "TEXT")
could clash with #35, #311 , #8270.
Square braces shouldn't clash.Tuple<int, char, string> foo = [ 1, 'A', "Text" ];
@AdamSpeight2008
I don't see how the proposed tuple syntax would clash with any of those proposals. #35 and #8270 both require a specific context involving the new
keyword. #311 doesn't use parenthesis or any other special characters, it only marks the return type as this
(and is not likely to be considered given #357). And even if it did, tuples appear to be much further along than any of those proposals so it'd be more likely that they would have to adjust their syntax.
Square brackets also imply arrays (or lists), not tuples.
@HaloFour Square brackets don't imply array or list, braces do though. eg VB's Array Literal {1,2,3,4}
c#
int[] a = {0, 2, 4, 6, 8};
@AdamSpeight2008
Check out #6949, there is interest on the team in supporting JSON-like initializers.
@HaloFour They still use { ... , ... }
and not [ ... , ... ]
, as using curly braces as set an existing precedent in the languages to mean _"collection like"_ eg array, dictionary, jagged array, etc.
{ key : value, key : value }
would still work.
Using bracket looks like a call with arguments.
``` c#
var x = ( 0 , 'A' , "BC" );
using curly braces, looks like an array / collection.
``` c#
var x = { 0, 'A', "BC" };
using angle brackets, would cause issue with operators.
``` c#
var x = < 0, 'A' , "BC" >;
using square bracket, makes it clear that this isn't a collection.
``` c#
var x = [ 0 , 'A' , "BC" ]; // ie; Tuple<int,char,string>[] x
and clearer when used in with a collection initialiser / array literal.
c#
var ts = { [ 0, 'A', "BC" ], [ 1, 'B', "CD" ], [ 2, 'C', "DE"] };
// ie; Tuple<int,char,string>[] ts;
@AdamSpeight2008 For the JSON object itself, yes, but in order to support JSON literals they would have to support JSON literal arrays, and that syntax is [ a, b, c ]
.
Let say we have
``` c#
void Foo(Tuple
then call it with the following
``` c#
Foo( [ 1, 'a', "apples" ], [ 42, 42, Math.Pi ] )
Using ( ... , .... ,...)
could make it very easy to think the method takes 6 parameters, not 2.
``` c#
Foo((1,'a',"apples"),(42,42,Math.Pi))
The use of square bracket, would break up the visual scan
``` c#
Foo([1,'a',"apples"],[42,42,Math.Pi])
@HaloFour
Prefix the json with a contextual keyword. json [ a, b, c ] // json array
and json { } // json object
@AdamSpeight2008
Using square brackets would imply that the method takes two arrays or lists, particularly if #6949 comes to pass. And using square brackets for tuples would completely preclude the option for ever considering them for list literals in the future.
Tuples are supposed to look like an argument list because an argument list is intended to feel like a tuple of the parameters. This creates a nice symmetry in the syntax and is used in virtually every language that supports both tuples and functions. Given that I think that your argument against it is pretty much invalidated by a _lot_ of precedent.
@AdamSpeight2008 Why not prefix all tuples with tuple
? Or method invocations with call
? Or everything with some new keyword? It's unnecessary. There's nothing syntactically confusing about using parenthesis for tuples _and_ using parenthesis for argument lists.
@HaloFour Then what's the point of have have language support for tuples?
When new tuple(1,'a',"bc")
about the same as tuple ...
.
VB.net already supports prefix method calls with call
The prefix for JSON makes sense as won't have the same grammar rules as C#, like VB's XML Literals.
How would you include from a .net object? other than the literals?
Wouldn;t this require some form of escaping with the literal? To be useful.
c#
var j = json { @{ from entry in dict
where entry.value >= 18
select key
}
}
@AdamSpeight2008
I was being sarcastic. I don't think keywords are necessary for any of the above because they are syntactically different enough. The tuple syntax is not ambiguous with method invocation any more than array indexing syntax in VB/VB.NET is ambiguous with method invocation. The similarities between tuples and argument lists are quite intentional, particularly given the symmetry with a function tuple return value and a function argument list.
It is just about as likely for there to be json literal syntax in C# as there was for C# to have xml literal syntax.
There is already object initializer and collection initializer syntax. Maybe they can be improved?
@mattwar I'd suspect the same, but that proposal is hanging out there. There were mentions on CodePlex as well as looking into dictionary/list literal syntax of some kind although I don't think any of that had been hashed out to any degree.
Either way, even if square brackets have no other potential use that can be foreseen I don't think it's necessary to look into using them for tuples over the currently proposed parenthesis.
With reference to @alrz proposals around splatting (#8987), I'd suggest @MadsTorgersen's splatting examples should use the following syntax to clarify that spatting is required:
public double Avg(int sum, int count) => count == 0 ? 0 : sum / count;
Console.WriteLine($"Avg: {Avg(...Tally(myValues))}"); // ... means map tuple to two params
// of Avg
And
var list = List<(string name, int age)>();
list.Add(("John Doe", 66)); // inner () needed to denote it's a tuple, not two
// separate params
@DavidArno No unsplatting occurs in your second example, removal of parentheses is the point. And the first one, I think that is just what I mentioned in #8987, anyways.
@alrz,
list.Add
takes one parameter. Without the inner ()
, it's being passed two. This would require "magic" to recognise Add
is expecting a tuple and to convert the two parameters into one tuple. If I had my own list class that had two overloads of add:
public class MyList<T>
{
public Add(T item) { // adds to end of list
public Add(T item, int position) { // inserts into element "position"
}
Then, when faced with myList.Add(("John Doe", 66));
for MyList<(string name, int age)>
, the compiler (nor anyone else looking at the code) cannot know whether I've given it "John Doe" instead of a tuple or forgotten a third parameter, which it'll magically map the second parameter of the method call. By making it a requirement to explicitly indicate a tuple, the following becomes much clearer, both to readers and the compiler:
myList.Add(("John Doe", 66), 25); // inserts the tuple into position 25.
That "magic" called unsplatting. and if there was actually an overload that takes two arguments no "magic" would happen. I think you misunderstood the proposal.
@alrz,
You are correct, I was confusing splatting and unsplatting. Therefore, what I was trying to say above can be more accurately, and simply, expressed as "I think the unsplatting proposal is a very bad one", for reasons expressed above. :)
About the unsplatting, I guess "params" are another interesting case for that...?
public void SomeMethod(int someValue, int otherValue, params (string name, int value)[] namedValues)
{
// do something with all variables here
}
SomeMethod(0, 1, "Someone", 2, "Someone Else", 3); // would that work?
SomeMethod(0, 1, ("Someone", 2), ("Someone Else", 3)); // I guess *that* should work?
Also, I prefer the idea of name-based splatting as opposed to position-based one (especially in the context of methods with optional parameters); though I guess splatting itself might get very confusing in itself, especially if applied implicitly. ^^"
I realize I'm a bit late to the party, but I'd like to propose an alternate approach. Let's take the original example (I'm not advocating any particular syntax, so I'll just use the syntax Mads used in the original post):
public (int sum, int count) Tally(IEnumerable<int> values) { ... }
I propose that this emit the following declaration under the covers:
public void Tally(IEnumerable<int> values, out int sum, out int count) { ... }
As previously mentioned, F# already supports treating this declaration as though it returns an int * int
tuple. We'd implement the same feature in C#, so both these lines would be valid:
(var sum, var count) = Tally(myValues); // no heap allocation nor struct copy
var tuple = Tally(myValues); // type of `tuple` is System.Tuple<int,int>
Benefits of this approach:
Considerations:
Task<Tuple<int,int>>
. I think the cost of using a reference type here would be dwarfed by the async machinery anyway. We would support deconstructing System.Tuple
in the same way, so this implementation detail would become transparent to the user:(var sum, var count) = await TallyAsync(myValues);
System.Tuple
instead of a new anonymous type. (UPDATE - I realize this is basically what @erik-kallen already proposed here, though I was thinking of a more generalized mechanism)Sorry for the long post, but I hope I've been convincing :)
@chkn It's interesting conceptually. Would that work for oneples, e.g. void F(out int x)
? Would the compiler disallow people from writing void F(out int x, out int y)
overloaded with (int x, int y) F()
?
IIRC, F# just uses System.Tuple<>
now. It can understand and transpose out
parameters but that's not the mechanism it emits.
@HaloFour I don't see why it couldn't work for oneples, but it sounds like we may not support those due to syntax.
As for conflicting overloads, I personally think the compiler should just disallow them with a compiler error, but I don't feel especially strongly about it one way or another.
You are right about F# -- the C# compiler would emit different code in this case, but you would be able to consume it out of the box in F#. Conversely, C# would be able to decompose System.Tuple
so you could easily consume F# methods that return tuples as well. The fact that the compilers generate different code would be an implementation detail you wouldn't generally need to worry about.
I really like this idea when I think that we could go backward compatibility to all out
and ref
We could even do this for a non void function. Like TryGetValue
We could
(var exist, var value) = dictionary.TryGetValue(key);
I think tuples should be implemented in easy and limited way - v1.0:
This is enough for most needs.
@vbcodec The only real difference between what you've described and what is proposed is that instead of using System.Tuple
and it's kin that new struct
-based tuples would be added to the BCL and the language feature would depend upon that. This would reduce the number of allocations and improve performance in most scenarios. Although it might be nice if the compiler could fallback to System.Tuple
if targeting older frameworks.
@HaloFour
In my variant, count tuple names are limited to 7 (max Items in System.Tuples). Practically this is enough.
For improve performance (limit lengthy allocations), there are some possibilites but require modifications. Instead of using System.Tuple, other generic class TuplePool must be used. Objects of this class are pooled, and may be recycled. This class overrides Finalize() method, and may prevent itself from being GC-ed, by joining to the pool. Additionaly this class must have read-write items than just read-only, to help to be utilized again by method that return tuples.
@vbcodec
Sure, the number of elements of the tuple would be limited to the arity of the target tuple type.
As for "tuple pools", that seems way overcomplicated and a source of contention, not to mention would still require compiler and BCL support to actually utilize. Struct tuples eliminates the issues of allocation without requiring any such mechanisms and keeps the compiler story just as simple as using System.Tuple
.
@HaloFour
Thinked more about usage of tuples, and look like tuples based on System.Tuple may not be enough.
UI controls use reflection to read / write data from datasources, and for this reason tuples must be auto generated anyway. For the same reason, tuples must be mutable and be objects than structs, to avoid copying between containers. Excluding tuples from being datasources, could be big loss for them.
Pooling is bit complex but not much. This requires compiler suport, but not CLR support. Pools are hidden to devs, so no problem for them.
@vbcodec Tuples aren't meant to solve the problems of data binding and the like. They're ordered groupings of related values. The names of the tuple elements are not a part of a contract; they exist only to assist the developer.
Auto-generated tuples could not be shared between assemblies. If two methods in two unrelated assemblies use the exact same tuple layout you could not pass that value from one to the other.
Being a struct does not make a tuple immutable. The proposal states that they would be mutable. But of course since the tuple itself is not a reference changes to one local would not affect another, although you could pass by ref
.
Pooling would require an infrastructure to be added to the CLR that would have to exist so that the compiler could utilize it. The compiler would not be auto-generating some kind of pooling class and burying it in every assembly. That would defeat the purpose of being a pool. And since there's no reason for a tuple to be a reference type (and reasons for them not to be) there's no reason to go through any of that complexity anyway.
This sounds like a rehashing of the same arguments and conversations that led to the proposal being in the current state.
@HaloFour
Auto-generated tuples could not be shared between assemblies. If two methods in two unrelated assemblies use the exact same tuple layout you could not pass that value from one to the other.
Code and methods that operates on tuples must be type-ignorant. Only names and values matter. Type of tuples is even more not part of the contract than names. As i have stated previously, move tuples out of data binding is mistake. NET is mostly used in LOB apps, where databinding is basis of these apps.
although you could pass by ref.
To methods only. Containers do not store by ref
The compiler would not be auto-generating some kind of pooling class and burying it in every assembly
Why not ? There already exist many hidden factories, initializers, buffers etc to support some language features. Yet another stuff is not exception, but tradition. As for names I used 'Pool' term, but in reality I mean set of buffers, each for generated type. These buffers store tuples ready to acquire by code. After acquire, tuple is dereferenced from buffer. When code throw tuple, and GC detect it, then tuple is back to buffer again. This is very simple and straightforward, none real complexity here.
This sounds like a rehashing of the same arguments and conversations that led to the proposal being in the current state.
GitHub and Roslyn is all about 'democratized development', no matter you like it or not.
@vbcodec
Code and methods that operates on tuples must be type-ignorant.
The CLR is not type-ignorant, nor can it be.
Only names and values matter. Type of tuples is even more not part of the contract than names.
Tuples don't have names. Only value and ordinal matters. Type is important because the CLR cannot ignore it, otherwise an object[]
would be perfectly suitable.
As i have stated previously, move tuples out of data binding is mistake. NET is mostly used in LOB apps, where databinding is basis of these apps.
And tuples aren't meant to be used in those cases. They are a gathering of simple values to satisfy an operation where it shouldn't be necessary to define a contract. LOB/data-binding depends on contracts and depends on names. You want records, and those are a different proposal.
To methods only. Containers do not store by ref
Indeed, and that's exactly how long a tuple should survive. If you're using them in long-lived scenarios and depending on monitoring their values changing, you're using them wrong. Again, see records.
Why not ? There already exist many hidden factories, initializers, buffers etc to support some language features.
The compiler does generate some complex code to support very specific uses of features. But there is nothing about this feature that warrants anything complex. And the compiler generally doesn't generate swaths of infrastructure code itself, only a specific implementation.
GitHub and Roslyn is all about 'democratized development', no matter you like it or not.
Indeed it does, and I actually love it. This "democratization" is what led to the proposal above. I'm fine with challenging that proposal, and I'm even fine with trying to rehash topics that have already been debated, but unless you're introducing some new ideas or reasons you're likely to just get the same responses. No matter if you like it or not. :smile:
@HaloFour
Code and methods that operates on tuples must be type-ignorant.
The CLR is not type-ignorant, nor can it be.
This make me think about my propose #9595 about hasA style interface in generic constraint. This kind of feature can support tuple
Generic function could have generate specific function for each type include tuple. And if we would be able to mock name to match it temporarily this would be possible
@Thaina Maybe, but that's something that the CLR certainly does not support at this time and to have tuples depend on a theoretical CLR feature would push back the ability to adopt tuples anytime in the near future. Also, to have tuples depend on interfaces would also decrease their performance because, even if they were structs, you could not actually use them as the interface without incurring a boxing penalty.
@HaloFour I think in CLR, Generic don't make struct boxing even with interface. It work like generate new function that take that type and just look that if parameter match the constraint
That's why I propose things as generic
Although it will introduce complexity to the compiler and allow smelly patterns, it doesn't make any sense, from the language point of view, to limit the number of items in a tuple.
An octople can be made using an heptaple with the first six items and a final tuple for the remaining two. Meaning that, instead of:
// T1..T7 aren't type parameters but concrete types
ValueTuple<T1, T2, T3, T4, T5, T6, T7, T8>
we would have:
ValueTuple<T1, T2, T3, T4, T5, T6, ValueTuple<T7, T8>>
This hinders interoperability because this:
(v1, v2, v3, v4, v5, v6, v7, v8)
would be the same as this:
(v1, v2, v3, v4, v5, v6, (v7, v8))
@paulomorgado This is exactly what F# does today:
// x is a System.Tuple<int, int, int, int, int, int, int, System.Tuple<int, int, int, int, int, int, int, System.Tuple<int>>>
let x = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
I have posted similar proposal #9924, that generalizes support for tuples
Will Tuples' names be accessible via Reflection? If so, how? (since all Tuples have type System.ValueType)
@darkl
They parameters or return type should be decorated with atributes to indicate the element names. I imagine that it might be tricky to navigate in some cases where the named tuple is a generic type argument for some generic type.
I think this can be challenging, since a named Tuple type might contain another named Tuple type as one its generic arguments.
By the way, I guess it will surprise some developers that
(int a, int b) tuple = (3, 4);
Console.WriteLine(JsonConvert.SerializeObject(tuple));
Prints "{ Item1 : 3, Item2 : 4 }" and not "{ a : 3, b : 4 }".
Can someone explain the value of mutable tuples? I realize that the problems it causes are relatively small (yet they are real problems) but I fail to see any value in mutability in this case to justify even the small problems it causes. You can define a tuple variable and set its fields later in the method before returning but why not simply declare variables and copy them into the tuple at the end. Seems like better code anyway.
(int sum, int count) Tally(IEnumerable<int> numbers)
{
var t = (s:0, c:0);
foreach(int i in numbers)
{
t.s += i;
t.c++;
}
return t;
}
Can be just
(int sum, int count) Tally(IEnumerable<int> numbers)
{
int s = 0; int c = 0;
foreach(int i in numbers)
{
s += i;
c++;
}
return (s, c);
}
What is the case where mutability can be useful because I only see cases where it can cause confusion?
@Eirenarch
The proposal already mentions that by being a mutable struct it puts the choice of immutability into the hands of the developer. A readonly
reference would be immutable as the value would have to be copied anyway. By leaving them mutable they can avoid extraneous copying and also allow them to directly participate in ref
locals/returns.
@HaloFour copying of what? If they are immutable there will be no copying of parts of the struct because all intermediate copying would happen in locals and then the whole struct will be replaced at once. I understand that the proposal leaves the mutability aspect to the programmer but when would one choose mutability anyway?
Since tuples are rewritten to instantiations of a generic struct, that would have the unfortunate effect that tuples of pointer types are unsupported. That is, (int*, int*)
would be a compiler error. Yet another reason to allow pointers as generic arguments.
@Eirenarch AFAICT mutability only came about because it was decided to use value types. As discussed here, value types that don't enforce any invariants on their fields have nothing to gain from immutability.
Personally, I disagree with adding a new tuple type entirely. In the "Struct or class" section of the original posting, @MadsTorgersen said:
In 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.
I agree structs are preferable to classes _in that particular scenario_, but seem much less ideal if they are used in less ephemeral ways, e.g. passed around a lot, stored in fields on the heap, etc.
I proposed another solution, which optimizes that common ephemeral scenario, while using the existing System.Tuple
type when you need to pass around or store the tuple. However, I fear I've joined too late in the discussion.
Still there are proposal about ref return. So what it will be handle? Are the tuple will able to return ref?
I'm definitely with @Eirenarch here: making the tuple structs mutable is a weird decision.
The proposal already mentions that by being a mutable struct it puts the choice of immutability into the hands of the developer.
Why would you do that? That's a classic "giving the developer enough rope to hang herself" thinking. Make them immutable to guide them into the "pit of success" and be done with it.
value types that don't enforce any invariants on their fields have nothing to gain from immutability
If I understand this double-negative statement correctly, the simple reply is "ensure tuple structs have invariant properties by not supplying setters (ie, make them mutable)."
Is it just me or is it looking more and more like the correct way to go would be to compile tuple support into out
parameters?
It seems to have all the desired properties:
It seems like the only disadvantage is that out
parameters are something of a bastard child in .NET. Syntax is not nice, they are not part of the CLI so not supported directly by VB / F# / etc. In general it is considered bad design to use them...
So maybe the answer could be, just make a proper, nice, CLI interface for out
parameters and then just compile tuples onto that.
Why would you do that? That's a classic "giving the developer enough rope to hang herself" thinking. Make them immutable to guide them into the "pit of success" and be done with it.
Why couldn't let
do that instead of the tuple type?
@GeirGrusom,
Where is the advantage in making the new tuple mutable by default (despite the fact that the current tuple types are immutable by default), then requiring new language functionality (let
) to allow compile-time-only checks that it's being used in a mutable way?
Just make it immutable, so that it behaves like existing tuples and requires the developer to work hard to write code that is more likely to break.
@glopesdev, how would that work for async
methods?
@paulomorgado this is the same problem with normal functions. If a function returns int, it needs to be changed to Task<int>
in order to return the value asynchronously. In this case, you would need to return some kind of Task
type. Basically i'm just echoing the proposal of @chkn above.
I guess the number one rule is that tuples should not survive the context of the function call. Generating new types seems like asking for trouble. If async/await does not support out
parameters, then maybe they should actually, and then there would be no problem.
@DavidArno
Where is the advantage in making the new tuple mutable by default
Mutation is an optimization opportunity.
despite the fact that the current tuple types are immutable by default
var tp = new Tuple<int[]>(new [] {0});
tp.Item1[0] = 1;
System.Tuple is not immutable, It's read-only. C# cannot express immutable types except by convention. I know you already know this, but I think it's an important thing to keep in mind.
then requiring new language functionality (let) to allow compile-time-only checks that it's being used in a mutable way?
Tuples are also a new language functionality. I wouldn't make a big deal out of it if tuples were made read-only, but if there is another language feature that can express the exact same thing without disallowing mutable tuples I hardly see the point of making the tuple type itself readonly. No one can know the design requirements of every application in existence, and therefore I think it's unreasonable to apply a read-only requirement on everybody.
@glopesdev
If async/await does not support
out
parameters, then maybe they should actually, and then there would be no problem.
That would involve promoting the out
pointers to fields where their lifecycle could no longer be managed by the compiler and there could be no guarantee that the target is still alive or on the stack. In short, async out
is immediately unsafe
territory.
@GeirGrusom: The problem with mutable structs is that expressions like listOfIntIntTuple[0].Item1 = 3
are not going to have the intended side effect. That, however, is to be expected as it is just like for any other mutable struct types. Still, mutable structs are often a source for confusion for developers new to C#. It might be that ref returns make this less of a problem in the future, though.
@HaloFour isn't that why we have a garbage collector? I'm not so much worried about which exact code the compiler should emit, but more that, from a language design perspective, tuples should not survive the context of a function call.
In a regular call, this is the moment where you call ()
the function, in async is when you await or otherwise register a consumer, but the point is that _the tuple exists only to manage the relationship between caller and callee_. If it survives that relationship, then you have design issues.
@glopesdev No, the garbage collector only works with reference types. out
parameters use pointers which are completely outside of the garbage collector's domain, but the compiler limits how they are used to specific contexts that can be guaranteed to be safe. The async method state machine would require promoting those parameters/locals to fields which would immediately make them unsafe.
The relationship between the caller and callee in an asynchronous method is largely faked. As soon as the async method awaits
that relationship has ended. There's no requirement that a caller await
on a method that happens to return a Task
. There's no reason to if the caller doesn't need to be asynchronous itself. The compiler isn't even aware that the callee happens to be await
.
tuples should not survive the context of a function call.
If you want them to work inside async
you would need it to do exactly that. The out
would have to be still available in the continuation (which is a different method call) but the compiler could give no such guarantee without moving the out
into a closure. The original calling stack frame can be dead when the continuation is invoked.
@axel-habermaier I believe that was one of the reasons why it wasn't considered a problem for the tuples to be mutable. The issue of having shared state mutated is effectively eliminated unless the caller explicitly opts-into it by either overwriting the struct or by passing it as ref
.
@GeirGrusom the point was that in the async world, a function call
is not tied in to the stack. It's rather a more abstract concept, which does involve some degree of framework management. Still, conceptually you can think of it as a function call (or at least that's what async/await is supposed to make us believe).
@HaloFour of course you can't rely on a stack, but that's why you have closures for, and closures are indeed managed by the GC.
I think in order to resolve this case elegantly we need to simultaneously move beyond function call _implementation_ and focus on function call _conceptualization_.
You can think of a function call as always receiving one input and one output. We just introduced parameter lists to avoid having to specify classes every time we call a function with multiple parameters.
Now we realize we want to do the same for the output. That's fine, but the concept of the function call should not change. Specifically, you don't get to treat the parameter list as an object (at least not in C#), so it's weird to treat an output parameter list as an object as well...
@glopesdev Closures are, but a closure that itself contains a field to a pointer to some arbitrary location that may have been on the caller's stack after the callee has returned is inherently unsafe.
You can't move beyond implementation because that's exactly what makes it impossible. To the caller an async method is no different than any other method. The caller will push pointers to its stack locations onto the stack and call that method. An async callee simply can't do anything safe with those values, at least not after the first await
.
@GeirGrusom
Mutation is an optimization opportunity.
When the tuple is found to be a performance bottleneck, the tuple is abandoned and a more performant option is adopted instead. Anything else is premature optimisation.
var tp = new Tuple<int[]>(new [] {0}); tp.Item1[0] = 1;
System.Tuple is not immutable, It's read-only.
System.Tuple
is immutable: the value of each of its properties cannot change. It's not truly invariant though as it cannot guarantee that - if those values are references - the contents of what's referenced cannot change. For reasons that I can't find, the language team seem to have decided that since this would apply to struct-based tuples too, there's no point in making them immutable. This is an illogical conclusion in my view,
Tuples are also a new language functionality. I wouldn't make a big deal out of it if tuples were made read-only, but if there is another language feature that can express the exact same thing without disallowing mutable tuples I hardly see the point of making the tuple type itself readonly. No one can know the design requirements of every application in existence, and therefore I think it's unreasonable to apply a read-only requirement on everybody.
It's the "genie out the bottle" principle. If they are made mutable from the off, then reversing that decision would be a breaking change. If they are made immutable by default, then - if it were found that there are many real-world use-cases for making them immutable, making the change in that direction in a future version would not be a breaking change.
@HaloFour maybe I'm missing something really fundamental, but I was under the impression that in C# you don't really need to think about pointers and stacks when you call a function. Sure, they do exist, but conceptually, at a language level they have been abstracted away.
This has been moved further with the support for async/await, where now we think of a function call like this: await Tally(list)
. This is a big leap, whether you support parameter lists or not.
I realize maybe the source of confusion is that I started by mentioning out
parameters. But in fact, the most important point of this suggestion to me is that tuple construction/deconstruction does not survive the context of a call. If out
parameters place unbearable pressure on how to handle pointers internally, then drop it, but this still does not mean it makes sense to generate arbitrary types automatically just to handle multiple outputs... I'm really sorry if I'm not able to make myself more clear.
@glopesdev
maybe I'm missing something really fundamental, but I was under the impression that in C# you don't really need to think about pointers and stacks when you call a function. Sure, they do exist, but conceptually, at a language level they have been abstracted away.
Which is true, but that's why the language limitations exist, the compiler forces you onto rails to prevent you from having to think about it. ref
returns/locals opens the rails a little bit but if you look into the limitations that the compiler continues to force you can start to see where the pointers really exist.
If the argument is that the tuple shouldn't survive between constructing the return value and then deconstructing the results I can understand that, but that would eliminate such use cases as returning a list of tuples or multikey dictionaries (explicitly mentioned as a use case for tuples at //build/) where you are required to think of the tuple itself as its own type.
@HaloFour then why not simply follow what's being done for anonymous types and just treat these tuples as _unspeakable_ types, i.e. you never actually mention them by name or declare them in any explicit way in C#, and they should always be constructed using type inference. Specifically, this makes a strong argument for supporting something like:
var Tally<T>(IEnumerable<T> source)
{
...
return new (sum, count);
}
@glopesdev
This is discussed in the proposal under the heading "Unification across assemblies". The other problem with "unspeakable types" is that if they're public they can't be unspeakable. Their name and contract becomes a part of the public contract and can never change otherwise it will break all consumers.
@HaloFour I guess I do see the value in these points...
re. mutability, I don't find it to be such a huge issue given that readonly
does protect against modifying members of the struct (somehow I wasn't aware of this).
Specifically, the following is not allowed:
c#
struct Point { public int x; public int y; }
readonly Point p = new Point { x = 1, y = 2 };
p.x = 3;
If somehow you could declare readonly
for locals, that would be great, but local variables are already mutable by default anyway, so this doesn't seem hugely inconsistent.
@glopesdev
"unspeakable types"
Sounds Lovecraftian, "Von unaussprechlichen Tupeln", a collection of writings about values that cannot be instantiated without losing your sanity or causing a VEX exception.
@orthoxerox as Douglas Crockford said, being a programmer is indicative of deep psychological issues :]
Will there be an explicit conversion between ValueTuple and KeyValuePair/System.Tuple?
It would be nice if we were allowed to write:
C#
foreach((int id, Person person) in dictionary) //could also be a collection of System.Tuple
{
Console.WriteLine($"The last name of {id} is {person.LastName}");
}
@or150,
Rather than an implicit conversion, would we not be better off with an AsTupleEnumeration
extension method for such situations? Eg,
public static IEnumerable<(TKey key, TValue value)> AsTupleEnumeration<TKey, TValue>(
this Dictionary<TKey, TValue> dictionary)
{
foreach (var keyValue in dictionary)
{
yield return (keyValue.Key, keyValue.Value);
}
}
...
foreach((id, person) in dictionary.AsTupleEnumeration())
{
Console.WriteLine($"The last name of {id} is {person.LastName}");
}
(Not sure IEnumerable<(TKey key, TValue value)>
would be valid syntax, but syntactic tuples would have to work with generic collections somehow).
@or150
This brings back an earlier discussion about being able to decompose any Object, not just Tuple. Since I propose that decomposition is by name, with automatic case conversion for first letter, under my proposal, the code might look more like
foreach((int key, Person value) in dictionary) //could also be a collection of System.Tuple
{
Console.WriteLine($"The last name of {key} is {value.LastName}");
}
// Create a 7-tuple.
var population = new Tuple<string, int, int, int, int, int, int>(
"New York", 7891957, 7781984,
7894862, 7071639, 7322564, 8008278);
// Display the first and last elements.
Console.WriteLine("Population of {0} in 2000: {1:N0}",
population.Item1, population.Item7);
// The example displays the following output:
// Population of New York in 2000: 8,008,278
issue:
population.Item1
Question: How can we rename Item1?
Proposed Solution:
Extension property?
population.City = p =>p.Item1;
This will bring question if its possible to access/set property with string
Something to map
List<string> keys=new List<string>(){"City","item2","item3","item4","item5","item6","Total"};
for(int iTupple=1;iTupple<=population.Properties.Length;iTupple++) //foreach Item1,Item2,Item3 so on...
{
population[keys[iTupple]] = p=>p[$"Item{iTupple}"];
}
Wondering if intellisense will work?
var debug=population.Total;
How to declare now?
var population = new Tuple((string City,int item2, int item3, int item4, int item5, int item6, int Total)
{
{ "New York", 7891957, 7781984, 7894862, 7071639, 7322564, 8008278}
}
);
Can we declare this dynamicly?
var population = new Tuple("(string City,int item2, int item3, int item4, int item5, int item6, int Total)"
{
{ "New York", 7891957, 7781984, 7894862, 7071639, 7322564, 8008278}
}
);
var debug=population[0].City;
Awesome, you know I have this csv/xml with 300 fields inside
Can I have strong typing?
var lines = File.ReadLines("population.csv");//Right click visual studio, call Roslyn
var headers = lines[0];
var typeDeclaration = headers.Select(h=>$"string {h}").Join((a,b)=>$"{a},{b}")); //just make all string
var population = new Tuple(typeDeclaretion){
{ "New York", 7891957, 7781984, 7894862, 7071639, 7322564, 8008278}
}
);
//Anyhow to call Roslyn to process above lines so we can have strong typing?
//F# type declaration can do it but bit messy somehow
var debug=population[0].City;
//it simply impossible then lets try something like this
var debug2=population[0].["City"];
@andrew-vandenbrink: but this is not what you should use a tuple for… why wouldn't you use a class?
@Miista I don't want to maintain class with hundred of properties
@andrew-vandenbrink you would rather maintain one or more tuples with hundreds of properties?...
This is exactly the scenario I feared would happen when being allowed to name tuple properties.
Den 15. apr. 2016 kl. 12.25 skrev andrew-vandenbrink [email protected]:
@Miista https://github.com/Miista I don't want to maintain class with hundred of properties
—
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub https://github.com/dotnet/roslyn/issues/347#issuecomment-210405145
@andrew-vandenbrink ,
@Miista I don't want to maintain class with hundred of properties
Then don't have a class with hundreds of properties.
@Miista,
Someone, somewhere, will abuse any feature offered to them by a language. The solution is education; not denying everyone else that feature.
@DavidArno
Someone, somewhere, will abuse any feature offered to them by a language. The solution is education; not denying everyone else that feature.
Aha. So then what's the problem with tuple mutability?
@axel-habermaier,
It's easier to teach someone to fall into a pit of success than to teach them not to fall into a pit of failure.
In other words there's nothing inherently wrong with mutable tuples. Immutable tuples, that can be made mutable when really required, would be an excellent new feature. Some would abuse it, but the majority would both benefit from them and learn better techniques whilst figring out how to use them.
No one ever tried with f# type provider?
Concerning mutability, if tuples can be passed as a single argument you run into the usual surprising behavior with mutable structs:
class Program
{
static void Main(string[] args)
{
var point = (3.0, 4.0);
Normalize(point); // doesn't actually mutate point
Console.WriteLine($"{point.Item1}, {point.Item2}");
}
static void Normalize(ValueTuple<double, double> t)
{
var norm = Math.Sqrt(t.Item1 * t.Item1 + t.Item2 * t.Item2);
t.Item1 /= norm;
t.Item2 /= norm;
}
}
If anyone doubts that this is surprising behavior in C#, consider that in C# virtually all types are either reference types or immutable value types (TimeSpan, DateTime, int, float, etc). Mutable value types are usually considered evil (1, 2).
@asik this is true, but its also the same behavior you would see by passing a single value as an argument. In the tuple case you are just passing multiple values.
@mattwar You mean this?
static void Foo(int value)
{
value = 4;
}
Not an equivalent example, you're re-assigning the parameter, not mutating the value referred to by the parameter. Here the behavior is consistent with reference types - re-assigning variables doesn't change the value that was referenced - and hence is not surprising.
@mattwar,
It's similar behaviour; it is not the same though. There is a significant semantic difference between reassigning a variable and mutating part of the value held by a variable.
@asik: Consider the following, where you assign a single bit of an integer:
static void Foo(int value)
{
value |= 1;
}
How is the tuple case any more "surprising" than the integer case? In my opinion, people that are "surprised" by this behavior haven't fully understood the difference between value types and reference types as well as pass by value and pass by reference. There's nothing surprising about this behavior at all. An "the usual surprising behavior" is a contradiction anyway :smile:
@axel-habermaier Again, you are re-assigning the variable, not mutating it. It may look like mutation but people are usually aware that a += b
is a = a + b
and so on.
Even if C# programmers understand the difference value types and reference types, it doesn't matter in 99.9% of the code they write because mutable value types are so rare (and usually frowned upon). So the example I wrote looks awefully like it's modifying the value passed in. It's the sort of trick code I would expect to see in coding interviews.
Mutability also doesn't play nice with structural equality. I think it's telling that when I implement ValueTuple in C#6 as proposed, Resharper complains with
Non-readonly field referenced in GetHashCode.
Consider:
var dict = new Dictionary<(int, int), string>();
var key = (3, 4);
dict[key] = "hello";
key.Item1 = 5;
Console.WriteLine(dict[key]); // oops, can't find my object anymore
This code isn't as evil as it seems because you're not modifying the actual dictionary key, only the local copy; still a confusing piece of code. I haven't spent a lot of time thinking about this problem but I suspect more subtleties and pitfalls will crop up. The more we'll get ref- and struct features in C# (ref returns, Array views, etc.), the more mutable value semantics will bite. At some point you'll wish you had const methods like C++ - please don't go there!
I am far from convinced mutable tuples buy us much of anything. If it's about avoiding copies, the compiler could be smart and optimize most of those away, i.e.:
public (int sum, int count) Tally(IEnumerable<int> values)
{
var res = (sum: 0, count: 0);
foreach (var value in values) res = (res.sum + value, res.count + 1);
return res;
}
This can entirely optimize away the construction and copy since the original value of res is not used after it is re-assigned. Anyway, we shouldn't worry about the cost of copies too much because tuples will be small - 2 or 3 items - in the vast majority of cases. F# avoided value types for its immutable tuples for this reason but it's now considered a mistake.
@mklemarczyk Java doesn't have tuples, C++11 tuples works fine and it works pretty good in other languages such as Python, Scala and F# and many others.
There's no reason why C# can't have features that exist in other languages, if you followed this feature you would know that there was a lot of feedback and work on it so I don't know why you think it wouldn't work so please elaborate.
There's nothing _magical_ here, it's pretty transparent as far as I can tell but if you have some concerns it can be quite helpful to write about them.
@asik
public (int sum, int count) Tally(IEnumerable
values)
{
var res = (sum: 0, count: 0);
foreach (var value in values) res = (res.sum + value, res.count + 1);
return res;
}
I think the C# compiler would have to forego the tuple in order to optimize this code. If the fields of the tuple are marked InitOnly
the C# compiler can't write to them, and the JIT compiler won't write to them.
I think new (byte, double) (1, 2)
should be forbidden because it's really confusing, I'd rather use #1203 in this situation e.g. (1 :> byte, 2 :> double)
or ideally, just a colon if it could be disambiguated.
@alrz
No need for new syntax:
((byte)1, (double)2)
// or
((byte)1, 2.0)
@HaloFour (T)e
is essentially a downcast, what we need here is just a hint to the compiler to infer a specific type (upcast/implicit conversion). Either way, I believe new (..) (..)
should go away, considering that new (..)
has a meaning now, it will be really hard to read or parse.
@alrz
That syntax is both and it is used by the compiler to hint as to types in a variety of scenarios. The following is legal, and the C# compiler emits no type conversion:
var x = new { y = (byte)1 };
Anyway, where exactly is new (byte, double) (1, 2);
being proposed? I don't see that syntax in this proposal or the two latest sets of design notes.
@alrz I see, so that's just an alternate (and probably more "technically correct") form of the following:
(byte, double) x = (1, 2);
I don't see why it's a big deal, most people would use the above anyway.
I think we've convinced ourselves in the C# design meeting that
new (byte, double) (1, 2);
is not going to be legal syntax. It won't be legal state a tuple type in a new expression. Instead, just use the tuple construction syntax.
(byte, double) x = (1, 2);
or
var x = ((byte)1, (double)2);
or
var x = ((byte, double)) (1, 2);
@mattwar Good to hear! new (byte, double) (1, 2)
is ugly.
I'd love to see the tuple literal treated as a collection initializer when it's assigned to an IEnumerable, so we could write
List<int> x = (1, 2, 3);
and have it treated the same as
var x = new List<int> {1, 2, 3};
I think that's cleaner than the proposed syntax of List<int> x = new() {1, 2, 3};
List<int> x = (1, 2, 3);
looks confusing to me. I would guess it to be explicitly instantiating a tuple and then implicitly casting it to a List<int>
. :-1: on some syntax where a tuple works as a tuple in some places and as a collection initializer in other places.
I agree. If there is any form of inferred List<T>
(or collection) initialization like that it would be more consistent to stick with curly braces, e.g.:
int[] array = { 1, 2, 3 };
List<int> list = { 1, 2, 3 };
HashSet<int> set = { 1, 2, 3 }
I'm not really a fan of this due to the inverted type inference anyway.
Tuples represent a loosely-bound collection of heterogeneous types, as such I don't know that it makes sense to offer a direct conversion to a collection of homogeneous types, even if the compiler can confirm that they are.
@bbarry, sure but if they were to allow instantiating a tuple and then implicitly converting to List<int>
in the same statement, then wouldn't it make sense for the compiler to optimize it by just instantiating the List<int>
straight away?
Right now it seems (from https://github.com/dotnet/roslyn/issues/11205 ) that they don't plan to offer such a conversion, but I don't really care for the proposed alternative of List<int> x = new() {1, 2, 3};
@HaloFour, I agree with you in general on wanting type inference to flow from the expression to the variable, not the other way around, but the reverse is useful when you're reassigning to an existing variable.
I actually like your curly-brace suggestion a lot, but I think it'd be nice if List<T>
is assumed unless otherwise specified (where T
is inferred from the types of the elements). E.g.:
var x = { 1, 2, 3 }; // List<int> inferred
x = { 4, 5, 6 }; // still List<int>
var y = HashSet<int> { 1, 2, 3};
y = { 4. 5. 6}; // still HashSet<int>
Edited to add:
Maybe HashSet<int>
in the above would need to be something like (HashSet<int>)
using cast syntax, but at that point I'm not sure if it's any better than just saying new HashSet<int>
. At least you could do HashSet<int> y = {1, 2, 3}
It's perhaps obvious that what I'm really craving is something like a List<T>
literal, but it'd be nice if it worked for arbitrary collections.
@timgoodman It's useful when you want to pass a list to a method,
F(new[] { 1, 2, 3 });
G(new() { 1, 2, 3 });
In this regard it's consistent with omitted type array initializer.
@alrz That case does have a nice symmetry to it, although I'd really prefer it if we could just say something like:
F({ 1, 2, 3 }); // Array inferred from signature of F
G({ 1, 2, 3 }); // List<int> inferred from signature of G
As the consumer of F
and G
I don't care whether what I was passing in was treated as a list or an array, so why should I have to specify it?
There's ambiguity I guess if there are two overloads of G, one that takes an array and the other a list. But it seems like that'd be a problem for G(new () { 1, 2, 3});
regardless.
We've talked about using target typing for an almost tuple like construction syntax.
KeyValuePair<int, string> kvp = new (1, "one");
It's basically a normal constructor call with the type being inferred from the context.
@mattwar I'm not particularly fond of the new
keyword in general, although I realize that ship largely sailed with C# 1.0. But in this case maybe omitting new
would allow a curly-braced initialization like in @HaloFour's example, without running into the ambiguity with anonymous objects.
@mattwar, I've been proposing that since var
was introduced.
It would be applied to any constructor call where type is know. Like fields, typed variables, method arguments.
@mattwar It should be var kvp = new KeyValuePair(1, "one");
Not KeyValuePair<int, string> kvp = new (1, "one");
Should not use tuple for construct type. We want to use tuple for typeless
@Thaina while what you're proposing can be pretty neat (_stronger_ type inference yay!) and if _possible_ I'd love to have something like this but I think that what @mattwar posted is applicable in different context, @paulomorgado elaborated on some of the scenarios.
Maybe they can add both? :D
I mean if we can do the following:
Foo(new (1, "one"));
There's no reason we shouldn't be able to do this:
Foo(new KeyValuePair(1, "one"));
And if we can do this:
var x = 1;
var y = "one";
We should also have the ability to do this:
var kvp = new KeyValuePair(1, "one");
The new (1, "one")
syntax is not a tuple construction syntax. It is a normal constructor call where the type name has been omitted. It is being inferred from the target. The better usage example would have been to show the method call composition Foo(new (1, "one"))
like @eyalsk showed because it is a place where the type is already known, but I was being lazy and didn't want to write out the declaration of Foo
.
So given a class/struct with a constructor:
public class Bar
{
public Bar(int i, string s) {}
}
And a method:
void Foo(Bar b)
you could invoke the method like this:
Foo(new (1, "one"));
(Moved to #35)
@alrz Can you please ask your questions on #35 ?
There are only two hard things in Computer Science: cache invalidation and naming things.
-- Phil Karlton
_rename_
One of the elements of this proposal is to make members accessible by names instead of positions. This allows to convey the semantics better. The ability to rename with an implicit cast is not in line with this.
(int s, int c) rename = Tally(...) // why not?
Firstly, I wonder how useful such an operation really is? If naming is hard, why make it easy to rename. If such an operation is considered useful, perhaps it should rely on an explicit cast.
var rename = ((int s, int c))Tally(...).
_optional names_
Personally I am a fan of naming the members. As in some cases the types can convey enough meaning, the names could be made optional. In case they are not provided, the compiler can use 'Item1', 'Item2', ...
(int, string) --> (int Item1, string Item2)
_sequential layout_
If I read the proposal correctly, the tuple type generated will be a struct with public fields with the names provided by the user. It is not mentioned explicitly that the order is preserved. This is useful for scenarios like serialization. struct layouts in C# are LayoutKind.Sequential
.
@tmds
Note that the names of the tuple members don't really exist. There isn't a separate tuple type emitted per use, like there is with anonymous types. Rather, all tuples will share the same underlying ValueTuple<>
types which only differ by their generic arity. To the CLR all of the property names will be Item1
, Item2
, etc. If the developer assigns names those names are effectively erased by the compiler. They survive method boundaries only in the form of attributes to hint to the compiler what the names were. But just as with parameters or locals those names are largely irrelevant and can be reassigned.
@tmds I don't think that just because something _might_ be useful and _maybe_ rarely used then it also means we need to make it _harder_ by forcing an explicit cast.
I mean tuples need to be lightweight and feel lightweight starting to cast whenever you want to have different names seems really odd because it's not like it's a different type, it's just _decorated_ with different names.
As far as I'm aware names are actually optional and I think that you can use Item1, Item2, ... to access the fields. (not sure if they are actually fields or properties.)
@tmds I don't think that just because something might be useful and maybe rarely used then it also means we need to make it harder by forcing an explicit cast.
Personally I don't think the rename feature is useful. If you use it, you need to be aware of the pitfall of positional arguments as pointed out by @EamonNerbonne. Casting is typically used cases where meaning may get lost.
@HaloFour do you know where I can read up on the attributes that would be emitted?
@tmds yup, I did say it's a good point! but I don't think that casting solves anything, in my mind we either need to have this feature or not at all.
I'm in favor of disallowing it completely but if they are planning to add it then I really don't think casting make sense because I don't think it improves anything, yes it tells the reader that it was renamed but if we go there, I prefer not to have it at all.
Currently, casting is used only when you want to cast from one type to another not where meaning may get lost, yes you can reuse it to mean something more but I don't think it's a good idea either.
Currently, casting is used only when you want to cast from one type to another not where meaning may get lost, yes you can reuse it to mean something more but I don't think it's a good idea either.
I think of (double sum, long count)
and (int s, int c)
as different types even if the compiler implements both of them using ValueTuple<int, int>
.
@tmds Why would the compiler implement both of them as ValueTuple<int, int>
? however, if you move from one type to another where there's no implicit casting for the members then it make sense to cast it; otherwise, I don't think it make sense, especially if you only tend to use casting to rename member names but like I wrote renaming seems like a bad idea to me. :)
When you reflect a method which was declared: (int sum, int count) Foo()
will there be a way to get the "sum" or "count" names? Or will those be lost when the code is compiled and will you see: ValueType<int, int> Foo()
?
@tmds
The method:
(int sum, int count) Foo() { ... }
would actually be something like:
[return: TupleNames("sum", "count")]
ValueTuple<int, int> Foo() { ... }
So you can still use reflection to find the names, but you'd have to inspect the custom attributes of the parameters and/or return types.
@HaloFour where can I find more info on the emitted Attribute?
In features/tuples it says:
Importantly, the tuple field names aren't part of the runtime representation of tuples, but are tracked only by the compiler.
Can we use tuple.0
, tuple.1
syntax for tuple element access? For tuples with over 7 elements (unnamed), ItemX
won't work and faking Item8
, Item9
fields doesn't seem to be a good idea.
@alrz that requires changing the grammar of identifiers.
A simpler option would be for tuples to provide an automatic indexer implementation, which would throw an ArgumentOutOfRangeException
when the index is not in [0..arity]
. Then we can use tuple[0]
, tuple[1]
, and so on.
If #6136 is implemented, we can even provide such an indexer ourselves for all tuples.
that requires changing the grammar of identifiers
No, I've proposed a change to the _member-access_ grammar, not identifiers.
Indexers won't work because each tuple element might have a different type. Actually I think ITuple
does expose an indexer which returns an object
.
@alrz
I like the tuple.1
syntax also. It's nice and clean. Would it be 0- or 1-based, though?
An untyped indexer that throws at runtime sounds like an awful idea. A last resort, maybe, but certainly not the primary way to readd the individual elements of a tuple.
@HaloFour
Both Rust and Swift have zero-based tuple access. But in C# it would be in disagreement with physical ItemX
fields, perhaps those fields should be inaccessible to the user and only be exposed via tuple.0
syntax.
Either way, my proposal is particularly against the idea of faking ItemX
names for "big tuples" mentioned in #11031:
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.
tuple[0]
not need to be real indexer. It could be just syntactic sugar that would be transpile at compile time. In other word tuple[0]
is equivalence to tuple.0
We may not be able to put variable as index, must be a constant, so it could tell the type at compile time
Or maybe, if we put constant at index it would became normal variable but if we put variable as index it would became dynamic
Would tuple[00]
and tuple[01]
become tuple.00
and tuple.01
?
Hey guys, C# does not permit to use a number prefixed names for variables - it would brake the naming convention.
You right @Thaina about that it does not need real indexer, because it would be hard to refactor code than.
Because Generic Classes does not have possibility to have a random number of properties. Do you want to provide a specific number of classes to provide this feature (like Action
I propose to use an Anonymous Types matching to the function signature:
public (int sum, int count) Tally(IEnumerable<int> values)
{
var s = 0; var c = 0;
foreach (var value in values) { s += value; c++; }
return (s,c); // It will return new { sum = s, count = c};
}
I think it is the best solution for all, it can be generated by compiler, and anyone will have the names that would like. Only problem is to adjust method signature.
Other propose it to treat tuples like an Anonymous Struct Type.
@mklemarczyk
Hey guys, C# does not permit to use a number prefixed names for variables - it would brake the naming convention.
They would not be the real names of the members, only synthetic names. Swift has the same rules governing identifiers; they cannot be prefixed by a number. But unnamed tuples elements are referenced by number:
let 1x = "Foo" // compiler error
let tuple = ("Foo", "Bar")
let foo = tuple.0 // assigns "Foo" to foo
Because Generic Classes does not have possibility to have a random number of properties. Do you want to provide a specific number of classes to provide this feature (like Action, Action ...)?
That is the proposal, ValueTuple<T1, T2>
through ValueTuple<T1, T2, T3, T4, T5, T6, T7>
. If you need more than 7 elements the compiler will automatically compound the tuples, e.g. a tuple of 9 int
elements would be ValueTuple<int, int, int, int, int, int, ValueTuple<int, int, int>>
.
If the compiler were to generate new anonymous types you would lose type equivalence across assemblies.
@Thaina I thought about this too, I mean reusing the indexer syntax for tuples but the issue with it is it's confusing, it is a special case and more importantly it doesn't behave like something that has an indexer because each member returns something else.
You don't need to return dynamic, you can return object and both aren't good enough because you lose type safety and I'm not even speaking about performance.
Besides, it's not clear whether they plan to support indexers and return an object.
@HaloFour but if you create a value anonymous type it does not lose type equivalence across assemblies because it does not exist for now, we can implement it to save type equivalence across assemblies.
Value types have other equals and compareTo implementations than objects.
Does anyone thought about GC? What will be with objects inside Tuples? When can be removed? Will are be disposed? Is it related at all?
I think so too. Indexer is less ambiguous. I just said I'm ok with it because it possible. Same go to tuple.0
because we just allow it with tuple and will just transpile at compile time, it not break anything exist too so both is fine
Maybe we could let both, tuple.0
is static syntax and tuple[0]
for dynamic
The syntax for accessing the unnamed members of a tuple is the same naming pattern as the declared members of the Tuple<...> class; Item1, Item2, ..., Item8, Item9, etc.
@mattwar Which is ugly and bizarre, especially considering that Item8
, Item9
, etc., don't exist. If the compiler is going to do something funky with the members anyway then why not do something clean and attractive?
@mattwar it's really unattractive to have Item1 to ItemN when we can have it better and succinct.
@mattwar Since Tuple<...>
types won't be constructable via tuple literals (...)
and they won't get "flattened" ItemX
names, it is irrelevant to keep it consistent with actual tuple types. I mean, it won't buy you anything to use the same naming pattern which in turn does need special treatment on the compiler side anyways. Also, non-existing ItemX
fields which won't show up in the reflection can be a source of confusion. Note that _all_ of the explicit tuple names will be erased at the compile-time, but in case of ItemX
, it would depend on the tuple length which is counter intuitive IMO. If we disallow proper element access via ItemX
or Rest
there is no need to exclude these names from possible tuple item names.
@alrz ValueTupe<...> also has member names Item1, Item2, ... I said Tuple<...> above, but I meant ValueTuple<...>
Rust also uses x.0
for tuple access.
Rust also uses x.0 for tuple access.
Which, in my opinion, is extremely confusing. Yes, I can get used to it but member names traditionally cannot begin with a numeral, and accessors are done via [...]
. That said, perhaps we needs a nice language operator for this type of action.
``` c#
var thing = new ValueTulple
int firstMember = detuple
detuple
```
The concept would be detuple(...)
would only work on tuples and expose their fields as an array of ordinals. Behind the scenes it would just use the offset of the field and read the data.
_Head meets the desk._
@whoisj
The argument being made is that the compiler could offer a nicer syntax than tuple.ItemX
. Involving indexers and some kind of type-case operator is certainly not nicer. _Maybe_ you could have the compiler treat tuple[0]
as tuple.Item1
which wouldn't require any type-cast or the like, but it would be strange for tuple[0]
to behave differently from tuple[i]
.
I'll also argue that tuples should not expose an indexer, at least not publicly, except through explicit implementation of ITuple
.
I'll add my 2 cents and say from a usability perspective, I don't really care whether it exposes tuple.ItemX
or tuple.X
. The difference in typing or comprehension is minimal, especially when you consider that with autocomplete you can type tuple.1
and then hit TAB for the rest to be completed for you.
From a consistency and discoverability perspective, given that the Tuple
type already exists and exposes members called ItemX
, I'd rather be consistent with that. Do Rust and Swift have a better syntax? Maybe, but this is C# / .NET, and in this ecosystem the ship has sailed already.
Whether the language does magic tricks to create a fake tuple.Item8
+ is really an orthogonal question. I'm of the opinion it shouldn't- if your tuple type needs more than 8 items you should be made to suffer by having to use the Rest
property. I'd even suggest MS should ship an analyzer that suggests to the user in such cases to make a real type. Pit of quality, guys.
Whether the language does magic tricks to create a fake tuple.Item8+ is really an orthogonal question. I'm of the opinion it shouldn't- if your tuple type needs more than 8 items you should be made to suffer by having to use the Rest property.
@MgSam At the same time, tuple literals (...)
do take care of the Rest
if it's a 8+ tuple. So it is natural to also provide a flattened element access. But certainly not with ItemX
because of the above reasons.
So it is natural to also provide a flattened element access.
Are there use cases where it will be considered good style to make an arity 8+ tuple that does not have names? I certainly can't think of any. We shouldn't encourage it by making it easier to write horrific code that someone else will eventually have to maintain. I think the reasoning should be the same as that which caused the BCL team to stop at a 7 arity tuple.
@MgSam Ok that makes sense. I agree that it should not create fake Item8
+ names even though it allows creating 8+ tuples via tuple literals. Nevertheless, I still prefer tuple.N
syntax for this.
@MgSam I agree with you for the most part but I don't think that the syntax needs to be the guardian of abusive usage! and by that I mean, I don't think that it make sense to design this feature in such a way that it would be unattractive and I'm not even sure that it will discourage people from abusing it.
Just my opinion but i think that a much better approach is to educate people about it and teach them where and how to use it.
I don't have a good use case for having a 7 arity tuple but if they are allowing it, then this use case probably exists? isn't?
@MgSam
We shouldn't encourage it by making it easier to write horrific code that someone else will eventually have to maintain.
So, that a vote against tuples then? 😉
Seriously, though, other than a few fairly specific use cases I can't imagine tuples being used for much else than by those developers that think that if they can declare everything on a single line that their code will run faster. I honestly fear what tuplified code will end up looking like.
@HaloFour It will probably be the same when var
was introduced back then, yes, some people didn't know when to use it and others even confused it with dynamic typing but the majority of people aren't _dumb_ so imo it wouldn't be such of a big deal.
@eyalsk
Features such as var
and anonymous types are at least limited to specific contexts and can never leak into the public contract. At most, you get some weirder looking local code and most of the time the inference is more welcome than not. It's extremely easy to leak tuples into public contracts and very easy to use tuples in place of more concrete types.
but the majority of people aren't _dumb_ so imo it wouldn't be such of a big deal.
Worse, they're _clever_.
Anywho, this is just an editorial and not very useful to the discussion of the feature. I'm not opposed to adoption of tuples in the language as a whole. The more I think about them the less I agree with the direction the team has taken. I probably would have preferred tuples to be a feature that could only be used internally within an assembly with compiler-generated structs. No assembly equivalence, no attribute-based erased names, no limited arity/elements. Basically an evolution of anonymous types.
@MgSam
From a consistency and discoverability perspective, given that the Tuple type already exists and exposes members called ItemX, I'd rather be consistent with that. Do Rust and Swift have a better syntax? Maybe, but this is C# / .NET, and in this ecosystem the ship has sailed already.
I see this as saying: C# is needlessly verbose (compared to newer languages), so we should continue to make it needlessly verbose instead of trying to catch up. My opinion is the exact opposite.
To me, one of the biggest benefits of tuples is their usefulness for quick prototyping and just trying things out in the REPL (now that Visual Studio _finally_ has a C# REPL). For these uses, defining classes for every collection of values you want to pass around feels like tedious overkill. C# is now competing with languages like Scala which were designed to be equally suitable for large projects and brief scripts. For developers who are used to being able to try things out quickly, the comparison is not very favorable for C#.
I also think that a more concise syntax fits with the overall trend C# has been following since at least C# 3.0, with type inference and lambdas, and more recently expression-bodied members and using static. This matches the trend of the industry as a whole.
Regarding tuple.0, yeah, it would look weird the first few times you see it, but then you'd get used to it. And the fact that it's different than tuple.Item1 lets you know immediately that you're dealing with a ValueTuple when you see it, which may be a good thing. And for those coming to C# from one of the multiple languages that already have that syntax, it will look immediately familiar. This is a good thing if the goal is to make the language attractive to new developers, and to developers who use multiple languages, not just to those who currently use C# exclusively.
@HaloFour
I probably would have preferred tuples to be a feature that could only be used internally within an assembly with compiler-generated structs. No assembly equivalence, no attribute-based erased names, no limited arity/elements. Basically an evolution of anonymous types.
I'm with you on this, these are my thoughts exactly.
@timgoodman
Regarding tuple.0, yeah, it would look weird the first few times you see it, but then you'd get used to it. And the fact that it's different than tuple.Item1 lets you know immediately that you're dealing with a ValueTuple when you see it, which may be a good thing. And for those coming to C# from one of the multiple languages that already have that syntax, it will look immediately familiar. This is a good thing if the goal is to make the language attractive to new developers, and to developers who use multiple languages, not just to those who currently use C# exclusively.
I agree with you on this but we don't really need to go the Rust way and have tuple.0 we can start at tuple.1 just fine, imo. :)
@timgoodman If you want make a Scala from C# maybe we need to create a branch from a C#?
You want to make a complex language that would nothing common with C and Java which are base of this language. Many developers are familiar with current C# syntax and I disagree to make too many new syntax if we can use existing one.
Right now Visual Basic have many problems to catch up C#. We all know this two languages uses the same CIL code.
@timgoodman If reducing the amount of time you spend typing by microseconds is important to you, then C# is not the right language for you. The cost of writing/maintaining correct software goes up exponentially as you move from development -> debugging -> running in production -> iterating without breaking anything.
Java is one of the most verbose languages there is and remains one of the most popular over 20 years later. Maybe you should try Perl? It's fabulous for creating read-only software really quickly.
My opinion is that I'd rather catch bugs when they're cheapest, at the "development" point of the lifecycle. In this case, changing the already existing idiom in the language just so that you can be like the cool, new kids on the block has real, non-zero costs. While, IMO, the tangible benefits are very close to zero.
@MgSam I don't know what others think and I don't think that it's so much about typing but with the new _proposed_ syntax when you write something like foo.1
you know that it's a tuple just by looking at it, much like you can reason about indexers today e.g. foo[0]
I think that it certainly make things easier.
With foo.Item1
we can only _assume_ that it is but it's not as obvious as it can be.
This might be a minor point, I don't know.
@MgSam
If reducing the amount of time you spend typing by microseconds is important to you, then C# is not the right language for you.
If that's true, then maybe it's not the right language for a lot of the developers out there who are happily using Scala or Swift or Rust or etc. But I suspect the C# team wouldn't mind appealing to those developers. In any case, I think your view of who C# is "right" for is too narrow. I've been happily using C# since version 2, and I've appreciated each of the many improvements that have been made over the years (such as type inference) to make it less verbose without sacrificing clarity. Sure, typing var
instead of Dictionary<string, List<string>>
or whatever might only save you "microseconds", but those microseconds add up. I think a great many C# users have embraced this trend in the language.
It's true that Java is extremely verbose. When C# was invented, it was largely competing with Java, but now it's also competing with modern languages like Scala, Swift, Rust, and so forth. I think it's not coincidence that most of the more recent popular languages tend to be more concise than Java, while still being highly readable.
By the way, if you think I have something like Perl in mind, you ought to take a few minutes to look at a tutorial on Swift. It's a very _readable_ language while still being concise. I'm certainly not saying C# should throw out its existing syntax and start over with Swift's. But they should continue to move forward with the evolution that's been going on at least since C# 3.
@timgoodman
I am all for new language features that reduce typing. But tuple.Item1
vs tuple.1
reduces typing by exactly one keystroke as I mentioned in my original answer. That benefit is not worth the considerable cost of being inconsistent with Tuple
, in my opinion.
I agree, Swift is a great language with a lot of interesting ideas! That doesn't mean C# should break consistency and backwards compat to make minor syntax concessions to the way Swift does things.
@mklemarczyk
You want to make a complex language that would nothing common with C and Java which are base of this language.
Adding tuples, even with a foo.1
syntax that would have previously been illegal, isn't going to make the language _nothing like C and Java_, any more than adding var
did. This is a gradual evolution, in keeping with the gradual evolution that C# has been undergoing for years. So far as I can tell, C# was _always_ intended as a language that would evolve with the times and incorporate the best ideas from other languages. The fact that C# has been much quicker than Java to adopt useful features like lambdas is part of its appeal.
@MgSam Many people also do code review and maintenance besides _typing_, typing isn't even a factor here.
When you see foo.1
you can easily know that it's a tuple whereas with foo.Item1
you can only assume that it is, tuples deserve their own syntax much like indexers deserve it and indeed have their own syntax.
@MgSam
That doesn't mean C# should break consistency and backwards compat to make minor syntax concessions to the way Swift does things.
There'd be absolutely no breaking changes as a result of allowing for numbered accessors to tuple elements. It is strictly new syntax. All of this is. The point being to make tuples feel like a natural part of the language. The fact that Tuple<...>
has existed for some time yet hasn't scratched the tuple itch is proof that the entire purpose of these proposals is to make tuples a first class citizen. Otherwise, there's no point to any of this work. We already have Tuple<...>
which works fine with C# syntax.
These are indeed all small points, but they're about the polish. If we're adding all of this new syntax just for tuples, and considering adding new syntax around accessors to flatten the syntax out anyway, why wouldn't we put nicer accessor syntax on the table?
I certainly don't advocate copying everything Swift does. A decent chunk of that language is just an awful mess of constantly shifting fads. But I do think that Swift's tuple syntax does feel really nice and really intuitive. And I think it fits in well with languages of a C/C++ heritage.
@MgSam
But tuple.Item1 vs tuple.1 reduces typing by exactly one keystroke as I mentioned in my original answer. That benefit is not worth the considerable cost of being inconsistent with Tuple, in my opinion.
Well, it also saves you a miniscule amount of time every time you read it (after the first few times when you're still getting used to the syntax). And miniscule amounts add up. In general, I think more concise syntax is preferable except where it makes the code harder to understand (which this wouldn't, after everyone has 5 minutes to get used to it.)
I also think it's worth considering that a primary reason for having tuples is to make the code more concise - otherwise, why not just declare a struct? So I feel like if conciseness is already a goal, why do it half-way? foo.1
also seems like less of a major syntactic addition than things like destructuring assignment (which I think is a valuable part of the proposal).
In general, I don't see great value in being consistent with System.Tuple. System.Tuple isn't that great at serving the intended purpose of being a lightweight way to group items together, although maybe it was the best that could be done with just a change to the libraries. It seems to me the point of this proposal is to give a more convenient tuple, at the cost of introducing new syntax.
I don't understand "not introducing new syntax" argument here, tuples alone, made a whole lot of new syntax already, so that the element access would be a trivial addition to the list. We have tuple types, literals, spreading, declarators (local and out var
), patterns, all of which has great impact to said idioms and day to day coding. I really don't think that the tuple element access would be a costly feature compared to the whole "language support for tuples" proposal. This is not about saving microseconds or keystrokes, it's about how deep tuples would be integrated into the language as a first-class citizen.
I expect most people will give their tuple elements names and access via those names, or use deconstruction syntax and pattern matching to get the elements out.
@mattwar, I really don't understand what you're saying here, are you saying that just because you expect people to access tuples by name of the elements then it doesn't worth the _efforts_ to have a better syntax for cases people want/need to access them by _index_? or to put it differently, people that do want/need to access tuples by their index need to suffer with the consequences of making their code less pleasant!
You have the chance to introduce a much better syntax and make it crystal clear that this part of the code reads tuple, I really don't know what to say...
I mean this can't be the argument here, "we expect that people will access the elements by name therefor we don't care about cases where people will want to access these elements by their index".
I suspect it won't be an issue for most people in most circumstances, and so it would be wise for us to not over design, and instead wait for more feedback.
@mattwar sure! I completely understand that but I think that the arguments against it, need to be more compelling.
@mattwar If that's the case (and I don't disagree with that reason) I would argue to not implement automatic flattening of big tuple properties at this point either until it can be demonstrated that it's common enough to require accessing members directly of big unnamed tuples.
I don't think it about saving some keystroke. But because tuple has its name when we create it. It shouldn't assume that it would be named Item
What if we create (int x,string Item0,float Item1) A = (0,"TT","TTTT");
What will you get for A.Item0
A.0
is obviously tuple
That's an interesting question. If you name the elements of a tuple are those names superimposed over the normal ValueTuple<...>
members?
(int x, int y) tuple = (1, 2);
int z = tuple.Item1; // legal?
If so, what if the tuple names are intentionally the same?
(int Item2, int Item1) tuple = (1, 2);
int z = tuple.Item1; // 1 or 2?
Swift does allow referencing a named tuple element by index, although they don't show in the autocomplete:
let tuple = (x: 1, y: 2)
let x = tuple.x
let y = tuple.1
Of course there's no concern for collision there since named tuple elements are subject to the same naming rules as all identifiers.
Simply put, you can't. See these methods.
@alrz
Well that's good at least.
Somehow I think maybe we should not just have tuple as fallback for ValueTuple<...>
but instead let tuple syntax generate new type. And then making the tuple access as functionality of all struct
I have been propose #9879 about the struct should be able to do something like C pointer to map type with the same layout size. If we could do this we could map by reference between any struct and ValueTuple<...>
and have structA.0
as syntactic sugar for ((ValueTuple*)&structA)->Item0
To me this (structA.0
) doesn't feel like c# if you want something like this why not structA[0]
you can implement this with no changes to the language and it has an advantage of allowing you this structA[someVariable]
And regarding the whole tuple unless you allow something like this
(int res1, string res2) data = await Method1Async(),Method2Async();
or
(int res1, string res2) data = await Task.WhenAll( Method1Async(),Method2Async());
possible I don't see the point, I mean it's nice but the tuple is most useful as a replacement for out
on async methods and as symmetry for params. If you don't make this possible when you add it to the language people won't use it .
@cordasfilip It should feel like something new; it is something new.
Co-opting the indexer syntax would be stranger because a tuple is not a homogenous data source. By using an indexer structA[0]
could return a different data type than structA[1]
, and structA[9]
could be a compile-time error. And how exactly would structA[someVariable]
work? The compiler would have no idea what data type that specific element happens to be within the tuple. Treating a tuple like an object[]
would defeat most of the purpose for adding tuples in the first place.
@cordasfilip There's no difference whatsoever between foo.Item1
and foo.1
when it comes to the syntax! you use exactly the same syntax to access members to me it makes complete sense and it's definitely C#-ish.
Reusing the indexer syntax is problematic for multiple reasons:
foo[i]
what do you return? object? dynamic? disallowing it? all these options aren't really useful for various reasons because the behaviour wouldn't match to that of an indexer.foo.ItemN
and foo[N]
aren't good from readability point of view and maintenance, because you can't spot whether something is a tuple at first sight, you have to check for it whereas (today) with bar.Something
you know exactly what it is and the same goes for indexers, you know it's an indexer when you spot one, tuples deserve the same!Tuples are new addition to the language, some people will use the _index_ to access the member of the tuple and when they do, there should be a succinct, pleasant and unique syntax for that especially when it comes to accessing members by index.
I see the only problem that you have is that you can not declare method that will return something like new { Age = 20, Name = "Steven"}
, and you are making a big change for that.
Next thing is that many developers are working in teams, so if someone will use it in code you would to do a code review or modify that part of code sooner or later.
Next issue is that the .0 .1
syntax will be found by text search when you are looking for a number in code even with match whole word option.
Last thing is that it is not alternative of out option at all, out keyword set a value for not always initialized variable and setting a value for that argument is not optional. More than that, argument is sent by reference even is it value type variable.
So stop telling me that new syntax is not a braking change and it is only alternative for out keyword.
You always can design your code in other way to return more that one result. I'm not against this feature, but I'm not sure that you are aware of all the implications?
@mklemarczyk Sorry, I didn't understand the whole post but I'll address your first and last sentences.
I see the only problem that you have is that you can not declare method that will return something like new { Age = 20, Name = "Steven"}, and you are making a big change for that.
No one have _problems_ returning instances of anonymous types out of their methods but people think about the consequences! you can't access members of the object without reflection or dynamic
, both of these things reduce performance and on the experience part you don't have intellisense and finally I have hard time to believe that people wants to allocate new objects on the heap with every call to these methods.
Tuples isn't a change, it's an addition to the language! there's a difference.
So stop telling me that new syntax is not a braking change and it is only alternative for out keyword.
You always can design your code in other way to return more that one result.
No one is telling you anything! people share their opinion, you can agree or disagree that's fine!
ref
and mutating the argument.Is someone thinking about array deconstruction? It could work in the same way as with tuples. This would be very useful, especially with async
, for cases such as:
(Foo foo, Bar bar, Zaz zaz) = await Task.WhenAll<object>(
GetFooAsync(),
GetBarAsync(),
GetZazAsync());
The example above would, of course, have to deal with casting, which may be sub-optimal. But there are still cases where this could be useful for a single type, like so:
(var foo, var bar, var zaz) = await Task.WhenAll(
new[] {"foo", "bar", "zaz"}.Select(GetSomethingAsync));
I prefer this,
(Foo foo, Bar bar, Zaz zaz) = await (
GetFooAsync(),
GetBarAsync(),
GetZazAsync());
but this doesn't need any special language support, you just need to write an extention GetAwaitable for tuples of various sizes. Would be nice if corefx provide such extensions out-of-the-box.
@alrz
Seems simple enough:
public static class TaskTupleExtensions {
public static async Task<ValueTuple<T1, T2, T3>> GetAwaiter<T1, T2, T3>(this ValueTuple<Task<T1>, Task<T2>, Task<T3>> tasks) {
await Task.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3).ConfigureAwait(false);
return new ValueTuple<T1, T2, T3>(tasks.Item1.Result, tasks.Item2.Result, tasks.Item3.Result);
}
}
That would probably cover 99.99% of the use cases, but the issue with doing that via extension methods is that you wouldn't be able to take advantage of custom awaitables or any of the effort being made with #10902.
@rdumont
``` C#
(var foo, var bar, var zaz) = await Task.WhenAll(
new[] {"foo", "bar", "zaz"}.Select(GetSomethingAsync));
is really confusing to me. As soon as you extract the `IEnumerable<Task<T>>` it gets very murky.
Are you saying that you want to take the first three elements, call the async method on them, await the three resulting tasks, and store the results sequentially?
Or, are you saying you want to project each element into a task, await all of them, and then get 3 of the results? The syntax suggests the former but the latter is what would happen. Even then, which 3 results do you want and in what order?
@alrz
``` C#
(Foo foo, Bar bar, Zaz zaz) = await (
GetFooAsync(),
GetBarAsync(),
GetZazAsync());
This is a lot better, and as you point out you can already write it as an extension method, but it's not clear if the awaits are intended to be sequential or concurrent. If tuples elements are like variables then the awaits should be sequential, but there's nothing to stop us from shooting ourselves in the foot since it's just a library method.
I was once tempted to write an implementation of Enumerable.Cast<T>
that looked like
C#
static IEnumerable<T> Cast<T>(this IEnumerable source) => source.OfType<T>();
I swear I had a really compelling use case but I didn't write it. I just thought about it and it was crazy and now I've confessed my thoughtcrime :]
@aluanhaddad it should be concurrent because that's kinda the whole point, otherwise, you could just await tasks sequentially.
var foo = await GetFooAsync();
var bar = await GetBarAsync();
...
@HaloFour not sure if C# type system allows you to generalize it for all task-like types. The compiler itself uses duck typing but currently there is no way for you to write anything based on those patterns.
@alrz actually after thinking it over I think that does make sense.
Looking at https://github.com/dotnet/roslyn/blob/master/docs/features/ValueTuples.cs I have some questions.
What tuple-vehicle will be accepted by compiler / generated code ?
Why ITuple indexed access is read-only ?
Think it should be read-write, as ItemX fields are also read-write
Regarding the naming of accessors; why not do what Scala did? Naming it _X
where is equivalent to ItemX
.
This would avoid the whole issue of creating special parser/grammar rules specifically for tuples.
@vbcodec
Why ITuple indexed access is read-only ?
Think it should be read-write, as ItemX fields are also read-write
Big reason I can see for this is that if you box a ValueTuple<>
you can then share that boxed reference and an update to one will be reflected in the others. Mutable shared state has been explicitly mentioned as an undesirable feature for tuples.
@Miista
Regarding the naming of accessors; why not do what Scala did? Naming it
_X
where is equivalent toItemX
.
That might be a bit of a nice compromise, although are identifiers prefixed with _
considered CLS-compliant?
Can we just call this feature union? C# is known to not support union till now and Tuple is known for System.Tuple
. The difference would be while union in other languages require specifying keyword, C# won't.
More terminology confusions:
.NET CIL: Common Intermediary Language
.NET CLI: Common Language Infrastructure
.NET cli (caps off): Command Line Interface (.NET Core)
@Miista but is it that bad? I mean it's not like that there are so many special cases and I don't think that there's something that makes more sense than this but if that's really an issue then I'd take _x
over ItemX
@jacobcarpenter union doesn't make sense and it will be even more confusing when unions will be introduced, if ever.
"Common Language Infrastructure" is the name given to the .NET specification whereas .NET CLI is the name of the tool used for .NET stuff so I really don't understand why would it confuse anyone, both makes sense in different context.
@eyalsk, I take it that you meant to @ me not @jacobcarpenter.
union doesn't make sense
Not sure if you know of C language union data structure, but care to elaborate why you said that? Saying no to everything without reason isn't helping in the discussion thread such as this..
so I really don't understand why would it confuse anyone
More distinguishable name could have been given to dotnet/cli project. I am sharing my opinion and it makes PERFECT sense to me!
@eyalsk It fixes the problem without introducing possibly confusing grammar
@jacobcarpenter I'm more of a C++ programmer than C so I wouldn't say I know C nuances, unions are useful but probably less common in C++ (depends on the work you do) than they are in C so I don't use them often, however, in C/++ a union
has special memory connotation that isn't applied to tuples so in my mind they are more similar to structs in nature than unions.
Besides the memory aspect of things, at some point union types might be introduced into the language where multiple types can be treated as single heterogeneous type.
Now, I didn't say no! and I certainly don't say no to everything, I said it doesn't make sense, there's a difference.
More distinguishable name could have been given to dotnet/cli project. I am sharing my opinion and it makes PERFECT sense to me!
Yeah... I didn't say you aren't making sense, I said I don't know why would it be confusing given that the context is very different, we use the same words in English in different context, why would this be any different?
@Miista What do you mean by confusing? do you mean that from a user perspective or language perspective? I guess you refer to the language but then I don't know why would it be confusing. :)
@jasonwilliams200OK
Can we just call this feature union?
Calling it a "union" doesn't make sense because the behavior as described in the proposal doesn't at all match the description of a "union" in terms of computer science. A union in that sense is a data type that represents one of a potential set of values. It cannot represent all of those data types, at least not simultaneously. Although in unsafe programming languages like C you can take advantage of the fact that the memory addresses overlap. With FieldOffset
you can simulate that same behavior in C#, so technically unions of blittable value types do exist in C# today.
An anonymous (or semi-anonymous) collection of loosely-related heterogeneous data types is a "tuple". That is the correct name in computer science. That is exactly what the proposal above is attempting to support. It doesn't matter that System.Tuple
already exists. This is about adding specific syntax to better support working with tuples.
@eyalsk User perspective: sometimes a property can start with a numeral, other times it can't.
@Miista I see your point but I think that just because we use slightly different rules for it, we can spot a tuple just by _scanning_ the code, I mean, let's take the indexer example, we create indexer almost like we create properties and yet we have completely different syntax to access them and it's great because it gives us immediate feedback on what it is just by looking at it, I don't think that tuples deserve any less and I don't think it's _confusing_ because it has a very specific role in the language.
If we have something like foo._1
then I can't possibly know what it is but with foo.1
I don't have to make any guesses or think twice, it's clear.
I think anonymous type's projection initializers are a valuable feature that is not available for tuples,
from item in list
select new { item.Foo, item.Bar }
If you want to avoid the allocation in the loop, you will end up with this,
from item in list
select (Foo: item.Foo, Bar: item.Bar)
It would be nice to be able to at least opt in to struct anonymous types for the sake of performance.
I saw @MadsTorgersen demonstrating tuples at NDC Oslo 2016 and I don't know whether they are still evaluating our feedback about the syntax but to me it seems like they are settled with tuple.ItemX
, unfortunately.
Can anyone clarify this @mattwar, @gafter? like what's the direction here? and if tuple.ItemX
is going to be the syntax to access members by _index_ then why is it better or preferred over tuple.X
?
First, I think the name "tuple" is confusing, as we already have a reference type called "Tuple" in its various generic versions.
While other languages may call these things tuples, they are really anonymous structs in .NET terms, should be directly analogous to anonymous reference types (principle of least surprise and all). The problem is, we really want to return these things from methods... that's the primary use case: returning multiple values without having to create a bunch of tiny types called XXXXResult or somesuch, and we can't do that with anonymous types.
Here's the issue, though: you can't use anonymous types as return values. Why would you allow anonymous structs to be? What problems with using anonymous types as return values aren't present when using anonymous structs? Seems to me in order to use anonymous structs, you'd have to solve this problem first, and then you'd be able to return anonymous objects as well! This leads us to...
Second, this is solving the wrong problem. All of this wrangling is simply to get around a limitation in the runtime: IL doesn't allow multiple return values from functions on the stack (like, say, python does). In fact, the _entire_ reason Tuple classes exist at all is to allow dynamic languages that support multiple return values to do so safely to the CLR. In fact, if you look at IL, all there is "ret", and the IL assumes that whatever you wanted to return is on top of the stack, and every function has a single return value.
Ideally, what we'd want is for IL to allow us to specify multiple return types, and push that many values onto the stack, and the calling site to know that it must unpack multiple return values, i.e.
public int, bool MultiReturnFunc(...) {
...
return 1, false;
}
var i, var j = MultiReturnFunc()
-- or --
int i, bool j = MultiReturnFunc();
-- or --
int i;
int j;
i, j = MultiReturnFunc();
This is the "correct" solution to this problem, but that's just not going to happen... too much depends on the assumption of "one return value on the stack" not just in the CLR, but in the reflection APIs, and even the base classes (i.e. Func types).
Instead of solving that problem we're trying to use anonymous structs instead. I'm not opposed to anonymous structs when you really do have a bunch of temporary objects you don't want on the heap. However, using them as a hack to support multiple return types doesn't make sense from a language level: it completely changes the rules vs. anonymous objects. Either make anonymous value types and anonymous reference types both returnable from functions (and thus also able to be used as parameters, or make neither of them returnable from functions.
Third, let's look at the syntax.
If you choose to allow them both to be returned from functions, I propose this syntax style for function declarations (similar for args):
public {int IntProp, bool BoolProp} MultiReturnAnonymousObject(...) {...}
public (int IntField, bool BoolField) MultiReturnAnonymousStruct(...) {...}
And for initialization:
var anonObj = new {IntProp: 1, BoolProp: false};
var anonStruct = new (IntField: 1, BoolField: false);
It mirrors precisely the anonymous object syntax, keeps the new keyword, and subtly hints that you should think of this as a constructor, rather than initializer (structs don't have a property initialization syntax like objects do, because structs _should_ be immutable).
union doesn't make sense
Not sure if you know of C language union data structure, but care to elaborate why you said that?
Unions in C[++] specifically deal with memory layout issues (since C[++] addresses memory directly): a union is a struct in which differently typed fields all start at the same address, and the size of the struct is the size of it's largest member. I honestly don't know of any other common programming language with a concept called a "union". It's a "false cognate" from a linguistic standpoint, and we should avoid that.
Given that C# is in the C family, naming it "union" would both imply 1) it has something to do with internal memory layout (it doesn't) and 2) it's somehow different from a struct (which it wouldn't be, other than the fact that you are letting the compiler name it for you).
@CleverNeologism
The fact that System.Tuple
already exists isn't relevant. The language feature could be built on top of that with little change to the proposal (e.g. they likely wouldn't be mutable anymore). The fact that it uses System.Tuple
or System.ValueType
or some compiler-generated struct is an implementation detail that doesn't change what the language feature is. The underlying type could be called Multiphase.Quantum.Defraculator
and the language feature would still match the expected behavior and functionality of the definition of "tuples" in computer science.
Ideally, what we'd want is for IL to allow us to specify multiple return types, and push that many values onto the stack, and the calling site to know that it must unpack multiple return values, i.e.
That would represent a pretty big change to the CLR, whereas this implementation provides the same performance without requiring any changes to the CLR at all. Tuples are also still desired outside of multiple return values, so implementing tuples satisfies both with less effort.
I agree that calling it a "union" isn't correct. Even ignoring C++, a "union" isn't supposed to behave that way. A "union" value can only be one of the elements, not all of them simultaneously.
@HaloFour, I actually agreed with your previous reasoning about computer science jargon, which is not to be confused with mathematics as; in set theory, union is composite of all (heterogeneous) constituents -- which is more like what syntactically dynamic
object represents in C#, which (again syntactically, impl. aside) seems very close to how multi-value return types would look like in the method signature. My point being, (very likely) confusion caused by just giving this feature name Tuple (when Tuples in BCL contract has gone viral) shouldn't be ignored, we can come up with some alternative name. In computer science, the closest cousin to Tuple is Record (https://github.com/dotnet/roslyn/issues/347#issuecomment-95737370), which in mathematics is also a Tuple:
A record can be viewed as the computer analog of a mathematical tuple. In the same vein, a record type can be viewed as the computer language analog of the Cartesian product of two or more mathematical sets, or the implementation of an abstract product type in a specific language.
//en.wikipedia.org/wiki/Record_(computer_science)
"record" "compound data" or something to that effect (but just not "tuple"), for sake of disambiguation..
@jasonwilliams200OK
A tuple may or may not be considered a record, and vice versa, depending on conventions and the specific programming language.
Records are distinguished from arrays by the fact that their number of fields is typically fixed, each field has a name, and that each field may have a different type.
Most modern computer languages allow the programmer to define new record types. The definition includes specifying the data type of each field and an identifier (name or label) by which it can be accessed.
Considering that names are completely optional I don't think this implementation qualifies as "records". Not to mention "records" are already proposed.
I seriously doubt that System.Tuple
enjoys a lot of usage in the market. Certainly not enough to satisfy the demand for "tuples" in C#. I think that the language feature will _very quickly_ eclipse the existing BCL type.
And the language feature might be enhanced to consume (not produce) System.Tuple
as a _tuple_.
I really have _hard_ time to understand why is it ambiguous? there's a different between what's in the framework and what's in the language! why people try so hard to come up with a better name when one already exists?
I mean when you want to use the types directly then you simply ignore the language and use them as is; otherwise, you let the language help you, It's more likely that people will fail to understand the difference between ValueTuple and Tuple than they will fail to understand what a tuple is and how to use it through the language.
It's not like we have two features in the language that are different and are named Tuples.
In my opinion, instead of discussing names seems like a better idea is to discuss their usefulness and syntax, I'm quite conservative with names myself but it's not like it was chosen out of the blue, it fits and describes exactly this feature, names are just syntactical magic anyway.
@halo4
It does matter that there is a type called "Tuple".
Would you add a language feature that had a name conflict with a type? It's why it's "Func" not "function", and creating a language feature called "convert" would be silly.
In fact, I use tuples often. When passing around small objects, but aren't certain exactly what the right naming/semantics are, I'll use tuples as placeholders until the design becomes clear. Thus, I want to be able to say "just use a Tuple for now" And not say "and I mean the reference type".
The biggest problem is that tuples are reference types as it stands. Having two thins, one value and one reference both referred to in the same way is unnecessarily confusing, and there's already a similar, better analogue in reference-land: anonymous objects.
I don't care what the underlying type is either, but when I say "return a Tuple" I want it to be unambiguous what I mean.
@CleverNeologism
Would you add a language feature that had a name conflict with a type? It's why it's "Func" not "function", and creating a language feature called "convert" would be silly.
And function is reserved keyword in C#? are we writing JavaScript or Lua...
I don't care what the underlying type is either, but when I say "return a Tuple" I want it to be unambiguous what I mean.
It's not ambiguous!
When speaking, I can't pronounce capital letters. It is ambiguous.
"Tuples are a bad feature." <- headline
Which do I mean?
@CleverNeologism oh dear.... you're joking right?
@CleverNeologism
"Tuples are a bad feature." <- headline
Which do I mean?
You mean the language feature to be introduced in C# 7.0, because System.Tuple
doesn't represent a language feature. It's what the vast majority of people would expect you to mean, because nobody considers C# to have "tuples" as a feature today.
Calling these things _anything_ other than "tuple" would be infinitely worse, especially more than a year from now when _nobody_ will remember (_or care_) about System.Tuple
.
when _nobody_ will remember (or _care_) about
System.Tuple
I disagree, when they are actively adding ValueTuple, ITuple etc. in CoreFX repo. This is a subjective line of argument at best.
@jasonwilliams200OK
I disagree, when they are actively adding ValueTuple, ITuple etc. in CoreFX repo. This is a subjective line of argument at best.
Doesn't matter, the vast majority of C# developers will use (int,int)
, not System.Tuple<int,int>
or System.ValueTuple<int,int>
or ITuple
. I doubt most developers will know (or care) what var tuple = (1, 2, 3);
even does under the hood.
Update: Fixed ValueType<int,int>
to ValueTuple<int,int>
@jasonwilliams200OK Why do you care what is added to the framework?
F# uses System.Tuple and guess what? it's called Tuples.
C# will use System.ValueTuple and guess what? it's probably going to be called Tuples.
Language X can even decide it generates the types with its cons and pros.
I'm sure you are more than capable to distinguish between the feature and its implementation.
Edit: Wrote ValueType instead of ValueTuple and System.Tuples instead of System.Tuple.
Is there gonna be a way to ignore return values similar to underscore in Golang?
@wanton7 Mads wrote something about it,
In other words is the form (_, _, _) = e; a declaration statement, an assignment expression, or something in between?
I don't know whether he actually referred to what you propose maybe someone else can clarify that.
But anyway I _think_ you can do something like this:
var t = foo(); // Say that foo returns (x, y, z) and we want to ignore x
bar((t.y, t.z));
This is a deconstructing declaration statement.
(int x, string y) = tuple_value;
and so is this:
(var x, var y) = tuple_value;
and so is this:
(int x, var y) = tuple_value;
and so it this:
(var x, *) = tuple_value;
and so is this:
var (x,y) = tuple_value;
This is a deconstructing assignment expression.
int x;
string y;
(x, y) = tuple_value;
It will probably have a return type of void for now, so you can only use it as an expression statement or in locations that allow a void returning expressions, like a lambda expression body.
This is just a regular declaration statement.
var v = method_returns_tuple();
This is a tuple literal expression.
(5, "yoyo")
It is target typed to System.ValueTuple or System.Tuple
You can write deconstructing declaration statements and assignment expressions involving tuple literals.
(int x, string y) = (5, "yoyo");
This turns out to be sort of equivalent to writing these declaration statements.
int x = 5;
string y = "yoyo";
But you'll be able to swap values without declaring a temp variable or using fancy xor tricks.
(x, y) = (y, x);
@mattwar but is there a way to ignore value when deconstructing? Like this code below where you only care about x and y.
(var x, var y, _) = GetXYZ();
if (x > 0 || y > 0)
{
// Do Stuff
}
(var x, var y, *) = GetXYZ();
@paulomorgado Thanks! I don't know how i missed that from the earlier post. I think my eyes are failing me :)
@mattwar Any news about the position syntax is t.ItemN
final?
Funny how people on the C# team deliberately ignore people when it suits them, I asked this question already and it feels like I'll need to ask this 10 more times before someone will bother to answer the peons in here, that's just rude!
Then when C# 7.0 is released there will be no surprises the same fugly syntax is going to stay because ignoring feedback and being less transparent is awesome! especially when it comes to being so rigid on decisions...
I don't mind "no" but I really like to know why not in the case you decided that you prefer t.ItemN
over t.N
! how hard is it to answer this??
@paulomorgado Is it because the syntax that some of us like or/and you disagree with the attitude I've shown in my post out of sheer frustration? I'm sure you have your days too...
There is no plan to have additional syntax for accessing tuple elements other than declared names or underlying ValueTuple names (which are Item1, Item2, ..., ItemN). You can also access tuple elements by position via deconstruction and pattern matching.
@eyalsk, both!
You want something to change. No reply, means no change. Pushing for it will just be annoying and rude.
As usual, you can always spec, implement and test the feature.
I'm always having moments. I might even be having one right now.
@mattwar Okay, thank you.
@paulomorgado Okay, I don't think that saying _nothing_ should translate into "no, we aren't going to change it" it's a lot better to communicate this.
So let's see:
I really don't think I'm the one that's annoying and being rude.
Now, I don't really think they literally ignored the feedback and ignore us but I think that saying something about it makes people less frustrated, it also provides more incentive for these people to share their opinions and contribute to the language, after all, I'm sure we're all here because we love the language and want it to be better!
@eyalsk
To be fair, I do believe that @mattwar did respond to the question. The conversation did continue but didn't really introduce anything new that might warrant further response.
However, I do agree that it does feel like things have been a bit less transparent lately. LDM notes have been pretty sporadic and even a number of recent issues filed by team members demonstrate shifts in syntax that are somewhat unexpected given the previous tone of conversations here. I'm going to assume that much of this is just due to "crunch time", particularly given the milestone that many of these issues have been updated to. But I also hope that the team can continue to take the collaborative nature of open source seriously. I don't expect it to be a democracy. I don't expect the team to consider or implement any particular proposals or suggestions made here. I'm thrilled by the prospect that I might be able to contribute, but most of all I want to feel like that little kid staring through the window of the candy store watching the caramels being made (because that sounds a lot better than sausage.)
@HaloFour Exactly, I don't mind them to say "no" and provide reasonable explanation for it, I don't expect them to implement all of the ideas that we post but at very humane level I expect them to communicate and collaborate more and it just doesn't feel like they do but like you said maybe it's just due to "crunch time", I really hope so.
Yeah he did respond but he wrote that they are waiting for feedback so I was curious what's going on with it.
Sorry guys, I get many hundreds of messages notifying me of git posts directed toward me each day, many thousands indirectly related of which I have over 67,000 still unread. One just caught my eye the other day, so I happily interjected into the conversation because I thought I could help clarify; and then I went back to my regularly scheduled day of meetings, etc. Did not mean to ignore anyone.
@HaloFour I wish we were making caramels... mmmmm
@mattwar It's all good, thank you very much for being so kind and taking the time to reply!
@mattwar
But compilers are sweeter than caramels =D
@HaloFour , @eyalsk
Maybe what this open process needs is to for Microsoft hire a community manager that can corral all the requests and take the brunt of questioning. Sort of an advocate for the community that operates inside Microsoft, since they are really driving most design and dev internally.
I think that might help since the devs cannot be everywhere at once and as far as i can tell they are the first and last line of defense to the almost 3,000 open issues in dotnet\roslyn right now.
@AlgorithmsAreCool Great idea and many companies actually do that, it can remove some of the pressure but it shouldn't come instead of developers actively communicating and collaborating directly with people in the community.
As open source project, you'd expect the team to be _more_ open to community suggestions but to me it feels, again, I'm not saying this is the case but that's how I feel, to me it feels like the process is one-way, when something is declined you want to know more about it, you want to know "why?" it was declined and if possible read what the team thoughts were when they declined it.
Like I @HaloFour said before it's not a democracy and it shouldn't be but thinking with the community and collaborating is important not just in terms of the language but to build a better community around it.
I understand that there's schedules and meetings or whatever but people that are part of this community are no different! we're just working in different domains and yet we find the time to be here and discuss things and personally I do it because I love the language and I think that the community is great! there's some very smart and talented people here.
Now, due to lack of transparency, they can tell you stories about lack of feedback when something is declined where in reality the story _might_ be very different.
Going open source is great and dandy, I applaud Microsoft for it and all the teams at Microsoft for pushing it, I love the new Microsoft but it can't be just for marketing! open source needs to mean something and currently it feels like the C# team is dangling behind teams such as the ASP.NET team, TypeScript team and VSCode team that are doing an outstanding job at building their communities.
I just hope that the C# team can improve their process and make their decisions more transparent to us in the future, maybe after C# 7.0. :)
@eyalsk
Lack of transparency ? Source code is available as well as thousands categorized issues. They are not obligated to post detaled desciption on every change they made to code, and are not obligated to answer any question.
For C# 7 and VB 15 design phase is finished. Now they are in implementation phase, where they do not need our feedback. Wait for C# 8 and VB 16 design pahse, for conversations and participate in development.
@vbcodec I fail to see how the fact that source code is available and the fact that there is thousands categorized issues has anything to do with what I said!
How does source code and categorization of issues helps me understand their decisions?
I didn't speak about obligations, everything I say is really a suggestion, I'm not here to tell them how to do their work, I'm here to voice my opinion and I think that if they want a better community, better feedback from us they need to improve their internal process and communicate more, they need to let more details out.
Telling me that they aren't obligated to anything isn't new, I know that, we all know that but it doesn't contradicts my point, things can be improved, that's all I'm saying.
Would it be supporting something like
public IEnumerable<(string name, int age)> SelectNamesAndAges(List<ComplicatedRecords> list)
{
return list.Select(x => new (string name, int age) { x.Name, x.Age });
}
I know you could just return a a list of anonymous objects, but this would provide so much more for the readers.
Given a class:
class Reader
{
public T Read<T>() { ... }
}
Will this last line compile?
var reader = new Reader();
var person = reader.Read<(string name, int age)>();
var name = person.name;
@no1melman You cannot return anonymous types in C# today, so your example is a major feature of this proposal. But the syntax would be more like this :
c#
public IEnumerable<(string name, int age)> SelectNamesAndAges(List<ComplicatedRecords> list)
{
return list.Select(x =>(name: x.Name, age: x.Age));
}
@tmds Yes it should work just like that
@tmds Yes is should work just like that
@AlgorithmsAreCool 'should' or 'will'?
@tmds.. Yes the language will flow the tuple type information into the return type of the generic. The 'person' local variable will have the type (string name, int age). However, I'm sensing an API like this might possibly rely on reflection to work. Since tuple element names are encoded as attributes, and not like typical member names, this could cause problems for API's that rely reflection, like typical serialization or data access API's. Tuples do have named members in metadata, but they are Item1, Item2, ..., ItemN.
@tmds My understanding of the proposal and associated documentation, leads me the believe that your example should compile and work as expected. So if i have read correctly, it 'will'.
But i have not tested this myself.
@mattwar Interesting. Is it beyond the compiler's ability to keep track of the types through the generic method call and map the members on the ItemN without resorting to reflection?
I figured that magic should work even after erasing the names.
I really hope that people won't use tuples in their public APIs.
However, I'm sensing an API like this might possibly rely on reflection to work.
@mattwar indeed, this leads me to my second question:
Will the implementation of Read be able to derive the member names?
@tmds
Will the implementation of Read be able to derive the member names?
No. At most the method Read
could inspect the generic type parameter T
, but all that would tell you is that the type is ValueTuple<string,int>
.
ok, thanks @mattwar @AlgorithmsAreCool @HaloFour
What's the status on tuple deconstruction? I've seen the syntax (var x, var y) = the_tuple
mentioned several times in this thread, but it doesn't seem to be implemented in VS 15 Preview 3, and the spec doesn't mention it.
@thomaslevesque look here
@thomaslevesque look here
Thanks!
@thomaslevesque no prob. :)
Why are we simplifying the process of writing illegible code? If there is a need to return a collection of related values, why not just create a class or struct which can name, describe and add context to the relationship.
@barry-jones Related values like KeyValuePair is Key-Value Pair. It just pair of key and value. It does not need a name at all for something that just have something as key and something as value. It already document itself that it is key value pair
To said it need to has a type KeyValuePair is like you need to write the word human on your forehead to be legit human
@barry-jones Everything depends on context, you can't say "some code is illegible" without providing context at all, Tuples aren't meant to be used as heavily as classes/structs and certainly aren't meant to replace them and in my opinion, like I've stated and some others before me should not be used in your public API again unless it really make sense.
Tuples might be used in functional paradigms more than OO paradigms but nonetheless they are useful just like KeyValuePair
or Tuple<T>
is useful in certain situations.
Tuples can be used sparingly in cases where you have a temporary data that you need to pass and it's just for this single method and you don't want to have hacks such as using arrays or passing everything by-ref or creating a class/struct just for this and I say hacks because all these have some pitfalls.
Not all classes/structs should be related in your domain model, just like not all people are related.
@eyalsk I like the suggestion about not using tuples in the public API. This makes sense as you cannot change them without breaking binary compatibility, and they're less descriptive about what they represent than a proper class or struct.
I think the inclusion of this feature in C# should come with a warning about why to use them sparingly, if at all, in a public API.
@Inverness
Why no tuples in public API ? Devs will faster switch to newest VS :)
@vbcodec Where I work, there is not a chance of that, unfortunately.
@vbcodec We shouldn't be dependent on the availability of intellisense to read/write code but then that's just my personal opinion, I'm not going to pretend like I have the rights to tell people what to do.
Looking back it looks like we might be better served by a more succinct syntax for declaring tuple types.
The problem with struct
is that just to make a value type "work" properly you need to implement IEquatable
, Equals
, GetHashCode
, a number of equality operators and ToString
.
There are canonical, well-known ways to write these procedures but we still need to keep recycling the same patterns again and again.
If I could just write:
tuple TallyResult
{
int Sum;
int Count;
}
and have the compiler generate a properly implemented tuple value type, the need for this feature would be almost none for me.
Deconstruction semantics into local variables would be nice, but these could apply to any type, not just tuples.
@glopesdev Isn't this records then?
Søren Palmund
Den 5. aug. 2016 kl. 02.08 skrev glopesdev [email protected]:
Looking back it looks like we might be better served by a more succinct syntax for declaring tuple types.
The problem with struct is that to make a value type "work" properly you need to implement Equals, GetHashCode, a number of equality operators and ToString.
There are canonical, well-known ways to write these procedures but we still need to keep recycling the same patterns again and again.
If I could just write:
tuple TallyResult
{
int Sum;
int Count;
}
and have the compiler generate a properly implemented tuple value type, the need for this feature would be almost none for me.Deconstruction semantics into local variables would be nice, but these could apply to any type, not just tuples.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
I was hoping to implement records by having a code generator read a RecordAttribute from partial classes or structs then use that to generate the remainder of the code based only on fields or properties that are defined.
@Miista I guess so, but it seems to fit what people are generally saying: tuple types are handy for private or internal methods, but records are preferable for public APIs and maintenance.
IntelliSense bookkeeping of tuple names doesn't really work for public APIs anyway, so it seems it would be better if tuples were limited to assembly internal methods only.
@glopesdev that's an extremely good point. That is something that should be taken into consideration with the documentation, coding standards etc. Otherwise you just know what the super eager junior devs of the world will do..
IntelliSense bookkeeping of tuple names doesn't really work for public APIs anyway,
It doesn't work in the VS 15 preview 3, but it will work eventually. The compiler will emit attributes to decorate the methods that return tuples.
Not quite. There are some cases where one just wants to return a small bag
of differently-typed values. Since the types are not compatible, you will
get a typing error if you use the wrong field.
A good example is returning an event and a timestamp. The types (and their
documentation) pretty much say everything there is to say.
On Aug 6, 2016 12:18 AM, "glopesdev" [email protected] wrote:
@Miista https://github.com/Miista I guess so, but it seems to fit what
people are generally saying: tuple types are handy for private or internal
methods, but records are preferable for public APIs and maintenance.IntelliSense bookkeeping of tuple names doesn't really work for public
APIs anyway, so it seems it would be better if tuples were limited to
assembly internal methods only.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/dotnet/roslyn/issues/347#issuecomment-238004849, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AGGWB0ZzszhUugqHJN51i6lufHdSzy51ks5qdAshgaJpZM4DeEL4
.
@DemiMarie Do you mean events in context of messages? because generally you aren't returning events but subscribing to them so I'm not sure I understand this part "returning an event and a timestamp", can you give an example? :)
Yes, I did mean messages.
On Aug 22, 2016 3:39 AM, "Eyal Solnik" [email protected] wrote:
@DemiMarie https://github.com/DemiMarie Do you mean events in context
of messages? because generally you aren't returning events but subscribing
to them so I'm not sure I understand this part "returning an event and a
timestamp", can you give an example? :)—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/dotnet/roslyn/issues/347#issuecomment-241335741, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AGGWB4hnrsey880xOUg5G9DjAEZ8mvLyks5qiVI7gaJpZM4DeEL4
.
@DemiMarie So in that case don't you think the returned type needs to have a name? I mean the name can hint about the source of the message, such as ChatMessage or ChatEvent, in my opinion tuples makes less sense in this context but then again this is purely subjective. :)
@DemiMarie, @eyalsk You even have the example of the generic Timestamped<T>
structure from Rx, which is the result of applying the generic Timestamp
operator to any input message stream. In this case you definitely don't want your timestamp to end up being called Item1
...
I have to agree the more I think about this the more it looks like records would be the way to go for public APIs, although it would be nice to have also generic ways of manipulating and converting between them.
+1 for splatting, enabling point-free use of multi-arg functions, e.g.:
int multiply(int a, int b) => a * b;
var squares = Enumerable.Range(1, 10).Select(i => (i, i)).Select(multiply);
Is there a way to just use the tuple return names as values without having to initialize a new variable.
static (bool period, bool comma, bool semicolon) Test(string mystring)
{
var r = (period: false, comma: false, semicolon: false);
//...
Instead it would be nice if there was a way to use the value directly:
`` static (bool period, bool comma, bool semicolon) Test(string mystring)
{
//It would be nice if could just access like
returnValue.period=false; // or just period=false;
//and could return as
return returnValue; // or return (period, comma, semicolon)
//...`
`
When I declare return value tuples with names, it feels like I should be able to use those names directly in the method just like when I name arguments I expect to be able to use those names directly. Is there some way to do that? Without it, it feels a little off....
Well, That's good syntactic sugar. And should be able to mix the normal one
return (period, comma, semicolon, x : 0);
something like that
Will unpacking tuples in a foreach loop be supported?
It'd be something like for x, y in entries()
in Python.
With this declaration:
private IEnumerable<(int x, int y)> entries()
{
yield return (0, 0);
yield return (1, 0);
yield return (0, 1);
}
I should be able to do something like foreach (var (x, y) in entries())
or foreach ((var x, var y) in entries())
.
@kupiakos
Yes, deconstructing directly in a foreach
appears to be supported. That's true of tuples as well as types that have a Deconstruct
method:
public static class KeyValuePairExtensions {
public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> pair, out TKey key, out TValue value) {
key = pair.Key;
value = pair.Value;
}
}
// later
var dictionary = new Dictionary<string, int>();
PopulateDictionary(dictionary);
foreach (var (key, value) in dictionary) { ... }
It will be nice if ValueTuple exposes Size property.
Considering following method:
public unsafe T[] ToArray<T>(ValueTuple<T, T, T, T, T, T, T, ValueTuple<T, T>> values) where T : struct
{
// it's hard to determinated by parameter type.
// More direct way is to use ValueTuple.Size property
var size = 9;
var result = new T[size];
// uses System.Runtime.CompilerServices.Unsafe
Unsafe.CopyBlock(
destination: Unsafe.AsPointer(ref result[0]),
source: (byte*)Unsafe.AsPointer(ref values.Item1),
byteCount: (uint)(Unsafe.SizeOf<T>() * size)
);
return result;
}
btw, it would be fancier if support Variadic Generics.
I had a go at using tuples with VS15 preview5, and have some feedback that I'd like to share with the design team and community. Apologies if some of these items are already in the pipeline.
Generally the developer experience is very good; however, there are several areas where tuples do not behave as one would expect; the following are things one would expect should work, and don't:
using Subject = (bool Gender, double Age)
```
from (a, b) in Pairs select a * b
from t in Pairs let (a, b) = t select a * b
```
Pairs.Select(((a, b)) => a * b)
('a', 1)
is a literal 2-tuple, then it should be possible to use ()
for a literal empty tuple. The empty tuple plays a very important role when programming functionally. At the moment, every functional program/library in C# has introduced its own Unit
type, given the lack of one in the BCL - and of course, they're all incompatible. With C#7, the nullary ValueTuple
would ideally become the standard empty tuple (and, yes, I would much prefer if it were called Unit
, as in any other language). But it should be usable, i.e.: return ()
, not return new ValueTuple()
@la-yumba
Actually kind of surprised that 2 doesn't work considering that you can deconstruct in a foreach
loop now:
foreach (var (a, b) in Pairs) { ... }
Could be an omission or just a detail not yet completed.
I want to say that I also saw comments that deconstruction in a parameter (3) would be supported, again could just be pending.
The using
case is interesting and I'm not aware of it coming up. I would question why you'd want to define a named alias for a tuple rather than just defining a simple struct?
As for unit and "womples", last I recall the team hadn't made up their mind but they weren't going to try to address them at this point: #10429
@mkjeff I'd like to see a generalized solution for this rather than adding this to the type itself something like countof(values)
that has no runtime overheads, in C++11 this would be sizeof...
and it might also be possible to do countof(ToArray)
to get the number of generic type parameters of the method itself.
@HaloFour
I would question why you'd want to define a named alias for a tuple rather than just defining a simple struct?
To reduce type noise. Types can already become pretty long, imagine if you have n types in an n-tuple! For example:
using OrderState = (LimitOrder Order, ImmutableSortedSet<MarketPrice> Prices);
// now you can write methods like:
Task<OrderState> Aggress(decimal amount) => //...
// which is much more readable than:
Task<(LimitOrder Order, ImmutableSortedSet<MarketPrice> Prices)> Aggress(decimal amount) => //...
@HaloFour Another reason is for tuples you have no control over. If a library returns (int, int, int, double)
(as some math libraries no doubt will), you might want to alias that thing.
@la-yumba @MgSam
Yeah, I was thinking about that same reason. Also, if you wanted something like a small struct but also wanted deconstruction support without having to write a Deconstruct
method manually. At least until records come into play which should provide a simplified syntax for that.
@HaloFour yeah, I think there will be a quick realization that tuples are just a very steep slippery slope to records, especially in a statically typed language like C#
@la-yumba All good points and all things we'd like to do eventually, but we need to stop somewhere, take a breath, and ship before we complete everything.
It would be better if:
1) Any tuple return with a defined variable name, the result should also convert this variable name as its property name……ect. Something like this:
var result = SomeTupleResult; //Suppose "SomeTupleResult" returns me (int a, string b).
result.Item1/Item2; //This is NOT exclipit
result.a; //Please convert "a" to its property, the same for b.
But for the anoymous definations, just convert to Item1,Item2……ItemN:
var Result = (1,2,3);
Result.Item1/Item/Item3;
Do we have this function now?
@MaleDong
That is how tuples would behave according to this proposal and as implemented in the previews/RC. If a function or property returns a named tuple then the compiler will allow you to reference the elements of that tuple by name.
We're actively exploring the idea of "erasing" the member names using attributes as you suggest.
@gafter Any conclusions drawn from the exploration of that idea? I'm guessing it's not making it into C# 7 but was anything deferred to be revisited in the future?
@atifaziz all tuple names get erased. They are not part of the underlying type. The names are encoded in attributes on the API declarations that use the tuples.
all tuple names get erased. They are not part of the underlying type.
@mattwar Yeah I get that.
The names are encoded in attributes on the API declarations that use the tuples.
Interesting and that's what I was hoping but I couldn't find these attributes in a test assembly with tuples that I compiled using the Visual-Studio-2017-RC
version in VS 2015. Would you know what these attributes are called, how they are decorated and where can the assembly containing them be found?
@atifaziz It is encoded in System.Runtime.CompilerServices.TupleElementNamesAttribute
Thanks @mattwar! Exactly the info I was looking for & glad to see it made the cut.
Being able to return multiple values without using ref/out parameters would be nice. So would be the ability to pass around things without creating single-use "model" classes. But overall I feel this proposal adds too much new syntax while solving a rather small set of problems. Too much cost/complexity for too little gain.
Positional assignments, deconstruction, mutability and conversions seem like features that could easily lead to severe maintainability issues. Actually, I will make a stronger statement: if introduced, they certainly will lead to severe maintainability issues, especially in large corporate applications. I'm saying this as someone who maintains several medium-size legacy apps in C#. Imagine dealing with implicit casts inside deconstruction statements. Or code that is using tuples to store and manipulate things throughout a 50-line method with multiple loops. This will be done, and this will be done a lot.
This proposal really describes several distinct features, and I think each of those features could be implemented in a more generic and broadly useful way.
For example, I often use current Tuples to pass composite models to MVC views. Unlike viewbags it ensures type safety, but in the process we loose all the attribute names. Inline type declarations really should solve this, but the current proposal doesn't seem to address such use case, and other similar use cases.
@model (IEnumerable<Thing> Things, int Page, int TotalPages)
...
Deconstruction sounds like a way to "capture" multiple assignments in one statement. But will it solve a rather common problem of partial object copy?
a.FirstName = b.FirstName;
a.LastName = b.LastName;
a.Age = b.Age;
Ideally, this should be possible without repeating names.
(a.FirstName , a.LastName, a.Age) = b;
But it sounds like deconstruction like this will not work without explicit Deconstruct implementation, creating which would defy the whole point.
...
Tuples won't help me when I'm calling int.TryParse("1")
.
Etc, etc.
In short:
Having syntax where method calls, tuple literals, inline type declarations and tuple deconstruction look the same will be visually confusing, and probably lead to incorrect mental models when new people learn the language. It's not like we're in Lisp where everything truly is a list. We're talking about four drastically different things here.
On the other hand, the proposed syntax is very different from what we already have in C# for very similar features.
Current type definitions:
class X { int A; string B; }
struct X { int A; string B; }
Proposal for what constitutes inline type definition:
var x = (int A, string B) { A = 1, B = "" };
Current multi-valued return method:
public Tuple<int, string>GetSomething()
{ /*...*/ }
This proposal:
public (int A, string B) GetSomething()
{ /*...*/ }
Implementation details aside, anonymously typed objects are intuitively similar to tuples: strongly typed entities without a "proper" class. Here is their initialization right now:
var x = new { A = 1, B = "" };
var y = new {A, B};
Here are tuple "equivalents":
var x = (A: 1, B: "");
return (A, B);
Note that anonymous object initializer # 2 captures names, while tuple return positionally assigns values, which is dangerous if we have a tuple with several objects of the same type.
C# 6.0 added a dictionary initialization syntax, which isn't strongly typed, but also deals with names and values, just like tuples:
var d = new Dictionary<string, string>{
["a"] = "1",
["b"] = "",
};
@reinventor, Please create a new issue about it, preferably new issue per feature or improvement.
@reinventor, Dictionary initializers is a C# 3.0 feature. What you're mentioning is index initializers which were introduced in C# 6-
Dictionary initializers rely on the implementation of IEnumerable
existence of a Add
(TKey, TValue)` method and indexer initializers rely on the existence of an indexer.
Objects of this type:
class D : IEnumerable
{
public void Add(int key, string value) => Console.WriteLine("Added({0}, {1})", key, value);
IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException();
}
can be initialized with the dictionary initializer syntax:
var d = new D
{
{ 0, "Zero"},
{ 2, "One"},
{ 3, "Two"},
{ 4, "Three"},
};
And objects of this type:
class I
{
public string this[int key]
{
get => null;
set => Console.WriteLine("[{0}]={1}", key, value);
}
}
can be initialized with indexer initializer syntax:
var i = new I
{
[0] = "Zero",
[2] = "One",
[3] = "Two",
[4] = "Three",
};
Yeah! Nitpicking! I know! 😄
I ran into two things playing with tuples today (VS2017 RC). The first is that there is no warning if you return a named tuple that does match the named order of return-type tuple:
public static (int sum, int count) Tally( IEnumerable<int> items )
{
var tally = (count: 0, sum: 0); // <- Whoops
foreach( var item in items )
{
++tally.count;
tally.sum += item;
}
return tally; // <- No warning that names are mismatched.
}
While I understand some might want this behavior it seems very error prone. So the current recommendation seems to not use tuples as local variables as the following will at least generate a warning:
return (count:count, sum:sum); // Warns that 'count'/'sum' mismatch return tuple.
If it was possible to declare a local variable using the return type, something like:
decltype(return) tally; // tally is declared as a (int sum, int count) and tally.sum / tally.count can be used.
....
return tally;
then this would at least eliminate some potential errors. I think #3281 already talks about a C++-like _decltype_ keyword. It would also apply to declaring a local tuple of the same type as a parameter. Maybe I missed a better way to do this.
The other minor issue is that in VS 2017 I think that the Formatting/Spacing rules need updating to include support for tuples as they are ignoring the normal ( )'s spacing rules currently.
@Ziflin
The tuple name mismatch issue was mentioned in #16674. I agree, I think that the compiler allowing for silent name transposition will be a major vector for bugs and have argued that point from the beginning.
Is there any chance that this will eventually be supported? I'm running into scenarios where tuple literals end up twice as long because I'm just repeating long names.
```c#
var reallyLongNameA = 1;
var reallyLongNameB = 2;
var tuple = (reallyLongNameA, reallyLongNameB);
Console.WriteLine(tuple.reallyLongNameA); // Errors, only tuple.Item1 is valid
```c#
var reallyLongNameA = 1;
var reallyLongNameB = 2;
var tuple = (reallyLongNameA: reallyLongNameA, reallyLongNameB: reallyLongNameB); // This works, but it seems unnecessary.
Console.WriteLine(tuple.reallyLongNameA);
Along same lines, what does a Func<>
signature look like for a Tuple
return type, never mind delegate?
@mwpowellhtx
Along same lines, what does a
Func<>
signature look like for a Tuple return type, never mind delegate?
Func <(int, int)>
@HaloFour Okay, so just that simple, ey? So in a real sense, Tuple returns are the answer to returned anonymous types?
Playing with Visual Studio 2017, I just discovered that, while I can do var (a,b) = SomethingThatReturnsATuple(); I cannot do
from thing in things
let (a,b) = SomethingReturningATuple(thing)
I don't see a reason why, var and let should have the same destructuring capabilities.
@tec-goblin There is a proposal to support that: https://github.com/dotnet/roslyn/issues/15074.
Tuples have been added to C# 7.0. We are tracking it at https://github.com/dotnet/csharplang/issues/59
Most helpful comment
Can we use "empty tuple"? If the empty tuple
()
is allowed, we can use it as Void or so-called Unit type which solves the issue #234 without CLR enhancement.