-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
C# Design Notes for Jul 1, 2015 #3913
Comments
Would the vb syntax be : Maybe it might be nice for named tuples to require an explicit cast ? Also will vb be case insensitive on this, and c# case sensitive ? Would casing differences result in an error in c#, but work fine in vb ? |
Let's say we have tuple (int, int, int, int) such as left, top, right, bottom. Is it really desirable to allow implicit cast to left, top, width, height ? |
|
I still don't see any value on the tuples themselves having names. I think that adds unnecessary complexity. If the implementation is kept simple and tuples are tuples (something that has If deconstrucion is the rule of law to name the items of the tuple, there's no need for the tuple items having names. Regardless of what he compiler can do with names. The only place where item names is useful is when describing the usage of the tuples and tooling (refactoring). From the top of my mind, these places are:
The naming could be solved by applying an attribute with the list of item names to element with a tuple value. That list of names can be an array, which has no problem with the number of elements it can have and perfectly compatible with the nesting of tuple definitions. This would also lift the restrition on names like Is there any compelling example that can't be served by this theory? |
Names are self-documenting. As described above the names of the tuple properties is akin to that of the names of parameters; they are not a part of the contract but they are still important in describing the contract. I'd personally prefer that the names be more formal as actual properties, but without some mechanism for structural equivalence in the CLR this is the best option. As for the array/indexer concept, you could do that today using |
@HaloFour, I never criticised the use of names nor advocated for an array/indexer API. I even gave a suggestion on how names could be attached to the usage of the tuple without being attached to the tuple itself. You can write somnething like this:
And it will be translated to:
Any tooling can pick up the names. And it's cross-assembly. On the caller side, you can either write:
or:
With an extra effort, this would be possible:
|
@paulomorgado From what you wrote before, it wasn't clear to me that that was what you meant, it sounded like you wanted users to write that attribute themselves. What's also confusing is that as far as I can see, what you're saying is exactly the same thing as what @MadsTorgersen proposed, but you seemed to be saying you disagree with the original proposal. |
@paulomorgado Oh sorry, you said that the list of names could be an array. Yeah, it could be. Adding "pseudo-properties" to the inferred tuple would be a nice convenience, too. That would be a novel use case for the proposed |
One of the things I disagree with is this:
Attaching the names to the exposing element (return value, parameter, field, etc.) removes the need to attach it to the value itself. After all, the names are meaningless for tuple equivalence. |
I believe that is what the proposal is saying, the attribute would be on the return, parameter, etc. public (name: (first: string, last: string), address: (street: string[], city: string, county: string, country: string) GetPerson((id: int, string: search) param) {
...
} [return: Tuple("name", "first", "last", "address", "street", "city", "county", "country")]
public ValueType<ValueType<string, string>, ValueType<string[], string, string, string>> GetPerson([Tuple("id", "search")] ValueType<int, string> param) {
...
} |
I agree with @Bill-McC's concern. Tuples are a good and necessary break from nominal typing. But if you can give tuple elements names, those names are integral to the structure of the tuple - they dictate what type this is and conveys semantics and intent just as much as, or even more than, the types of the elements. In this way, they would also make sense in comparison to anonymous types where not only the type matters but also the name as well as the position. A little flexibility is gained by allowing name decay, but at the expense of making them less robust building blocks. I am also definitely in favor of the nameless tuples (with no element names), where you explicitly strip any semantics and intent other than that conveyed by the types. Being able to say that is valuable too. Allowing you to send an (width: int, height: int) tuple into a function taking (x: int, y: int) does not make sense. It seems that some measures will be taken to prevent this, but that if going by a temporary variable, this could be circumvented. This is a bug farm. I want tuples to be lightweight and low on ceremony, not flimsy. Please require the names to match up at all times if given and keep this information around; that way, it will never be the source of any gotchas even if it means a bit more typing, and reordering could be included in a sane way. |
@JesperTreetop If anything I'd say those methods are bug farms with or without named elements. Tuples have their uses, but I think that this example is exactly where nominal typing is significantly more appropriate. What scares me that is that tuples will be slopped around in place of nominal types by people who think that saving a couple of keystrokes anywhere and everywhere is somehow a good idea. |
+1, @HaloFour |
Actually I can see some uses in graphics or game programming, where size can easily be used as a vector. I would give a different example: (age: int, numberOfPeople: int) vs (numberOfPeople: int, age: int) vs (numberOfPeople: int, dayOfMonth: int).
I can easily see that happening. Besides, there seems to be a strong overlap with the new |
@HaloFour: it is already happening. This is what people use anonymous types for and they fall off a cliff beyond which they will use hacks. A sane tuple design would prevent bugs and not cause them. We already have a weak tuple design with System.Tuple. It's time for the one people are asking for too. |
Why would one use named tuples over records? |
@dsaf: Fair question. If records are going to be in, they will likely be a better alternative than tuples with named fields. (I was under the impression that records were coupled with pattern matching as a long shot that probably won't make C# 7.) But tuples with named fields whose names can go away or that don't hold, in the way as demonstrated in the actual notes, are brittle and invite mistakes. At least tuples that don't have field names honestly reflect that nothing further is known, much like indexing into an array at a position by a bare number shows that there's no other structural information available. I would love records, and I would love having both records and tuples, but regardless of whether records make it or not, I would not love tuples that look like they could be used as records but that fall down in subtle ways when you do. I don't see the appeal with that at all. Don't provide "half" a feature, just drop the names at that point. If records do end up making it, and I hope they do, you're right, record syntax + tuples with unnamed elements only is probably the best combination. |
I would like to point out that giving names to tuple fields is not the same as the fields having names. Type (class or struct) fields have names even after compiled to IL. Method parameters and local variables don't. You name them on the source code but they don't have names on the IL. I don't think there's any value on tuple fields having names given that they are intended to have structural equivalence. I do see a lot of value in accessing them by name on source code. |
I have to say, I really don't understand the decision. Making tuples act like objects during consumption is a "why not objects" for me. Even in proposal it was indicated, that treating tuples/return values like real objects is strange, they are not logical units. Also named are set at the method, what if I write generic method used by many domains? It might be averageAge in one or averagePrice in the other module. Decomposition will have it, the named will have to be cast to variable or remember that result.Average might mean average price, since I sent Price type. |
How about to forbid tuples and allow to return anonymous object?
|
@KalitaAlexey, how is that any better? A tuple is a type that already exists and the type of tuple with two |
Anonymous object is better, because it more clear to get values from anonymous object in comparison with tuple |
@KalitaAlexey, you should read the whole thread. |
@paulomorgado I read the whole thread, but I think
is clearer than
because when values of a tuple has different types it works good, but for something like
someone could write
and it is going to compile, but is not going to work properly. |
@KalitaAlexey you're suggestion doesn't solve the problem you've expressed. I'm confuse. Enlighten. |
@whoisj You are wrong.
or
but with anonymous object for function
it is available clear variants
or
but here all fields will be match, because their names match rules. And also is available something like
It is more flexible, doesn't it? |
@KalitaAlexey What prevents tuples from having the kind of unimplemented decomposition capability that you are describing? |
@dsaf Tuple by default it is a values of possible different types. Only order is matter. I suggest to think that name is matter, but not order |
But the samples from Mads in the NAMES section say: " The last two are probably the only possibly controversial examples. When target typing with names in the literal, this seems very similar to using named arguments for a method call. These rules match that most closely." So when names are given, they have to match, not the position. |
By rethinking about it, I find it easier to think it as
Then what does not exist before is the tuple return value. It of course can be the returned ValueTuple object, but that does not work the same as parameter list. Because it combines elements into a structure. This also creates casing delimma, as public structure member should be PascalCasing per standard but tuple member and parameter list uses camelCasing. Then the idea of treating the return value and out parameters as the return tuple catch me. It is originaly raise by HaloFour? I don't remember clearly. But it matches the analogy very well. So the return tuple is still parameter list, the out portion of parameter list. Just some thoughts. |
Imagine this API:
Regardless of the name you give to the tuple items (or even if you give them a name at all), the first item will be |
@paulomorgado
|
You're right! Sorry! |
If tuples are mutable structs they will come with the usual pitfalls of mutable structs, i.e. http://blogs.msdn.com/b/ericlippert/archive/2008/05/14/mutating-readonly-structs.aspx . In addition, tuples should be structurally equatable, but that leads to strange behavior if they are also mutable. In general, structurally equatable types (types that represent values) should be immutable, and good principles shouldn't be violated without good reason. The performance penalty of copying struct tuples seems overblown, the same mistake that gave us reference type tuples originally (http://stackoverflow.com/a/5856728/154766). Tuples are useful as small records and should typically not contain more than a few elements; they should be designed and optimized around that common case. Anything larger would benefit from being promoted to a real class or record (if that makes it in C#7). For instance, it should not be a problem to have to "overwrite the whole tuple", because tuples are small data structures.
Perhaps compiler-generated equality code can pass the structs by reference. User-defined equality would be problematic, but that seems a strange feature to support for tuples. |
"useful pitfalls", there I corrected that for you.
So |
You confuse values and variables. 2 += 1 should be (and is) illegal because 2 is a value; x += 2 is legal if x is a mutable variable. |
Actually, Point p = new Point(2, 2);
p.Offset(2, 2);
p.X = 5;
Debug.Assert(p.X == 5);
Debug.Assert(p.Y == 4); |
Really, the compiler is allocating a new bit of memory every time I ask it to write to a value? I'm skeptical.
This is just writing bits to memory. How different is it from Neither is pretty, but both are useful and necessary (in the correct context). |
@whoisj The difference is that you're overwriting the entire memory space with a new value, not a portion of it. |
@HaloFour I see where you're coming from now. Are you suggesting that struct should only be written to if the entire struct is written to? Example:
So long as nobody has a |
Yes, because when you pass a struct you are passing the entire value. Requiring that the struct be completely rewritten avoids those cases where you think that you can mutate a struct but you're actually mutating a throw-away temporary value. For example: public class Foo {
public Point Point { get; set; }
}
static class Program {
static void Main() {
var foo = new Foo() {
Point = new Point(2, 2)
};
foo.Point.X = 5;
Debug.Assert(foo.Point.X == 2); // didja see that one coming?
}
}
This is perfectly fine because you'd at least be updating the reference to the new complete value. |
@HaloFour I'd support that if it were enforced. So long as they remain writable and I could still partially write with |
It is easy to see how confusing mutable struct tuples would be in a language that defaults to reference types. This would be weird to most C# programmers:
With immutable tuples, if the programmer really wants a different local copy, he has to make this explicit, and now there's no confusion possible:
Or let's say
This is reasonable-looking code that compiles without warning, but it doesn't actually mutate the elements of the items in the list; it only mutates the local variable
|
Design notes have been archived at https://github.com/dotnet/roslyn/blob/future/docs/designNotes/2015-07-01%20C%23%20Design%20Meeting.md but discussion can continue here. |
C# Design Meeting Notes for Jul 1, 2015
Agenda
We are gearing up to prototype the tuple feature, and put some stakes in the ground for its initial design. This doesn't mean that the final design will be this way, and some choices are arbitrary. We merely want to get to where a prototype can be shared with a broader group of C# users, so that we can gather feedback and learn from it.
Tuples
The tuples proposal #347 is pretty close to what we want, but has some open questions and unaddressed aspects.
Names
We want the elements of tuples to be able to have names. It is exceedingly useful to be able to describe which elements have which meaning, and to be able to dot into a tuple with those names.
However, it is probably not useful to make the names too strongly a part of the type of a tuple. There is no really good reason to consider (int x, int y) an fundamentally different tuple type than (int a, int b). In fact, according to the analogy with parameter lists, the names should be of secondary importance: useful for getting at the elements, yes, but just as parameter lists with different parameter names can overwrite each other, as long as the types match at the given parameter positions, so should tuple types be considered equivalent when they have the same types at the same positions.
Another way to view it is that all tuples with the same types at the same positions share a common underlying type. We will make that type denotable in the language, in that you can write anonymous tuple types,
(int, int)
, even though you cannot write an anonymous parameter list.For now we don't think we will allow partially named tuples: it is either all names or none.
All "namings" of a tuple type are considered equivalent, and are convertible to each other via an identity conversion. For type inference purposes, an inferred tuple type will have the names if all "candidate types" with names agree on them, otherwise it will be unnamed.
For method overriding purposes, a tuple type in a parameter or return position can override a differently named tuple type. The rule for which names apply at the call site are the same as those used for named arguments: the names from the most specific statically known overload.
Tuple literals likewise come in named an unnamed versions. They can be target typed, but sometimes have a type of their own:
The last two are probably the only possibly controversial examples. When target typing with names in the literal, this seems very similar to using named arguments for a method call. These rules match that most closely.
This is something we may want to return to, as it has some strange consequences. For instance, if we introduce a temporary variable for the tuple, and do not use target typing:
But for now, let's try these rules out and see what they feel like.
Encoding
Are core question is what IL we generate for tuples. The equivalence-across-names semantics make it easy to rely on a fixed set of underlying generic library types.
We want tuples to be value types, since we expect them to be created often and copied less often, so it's probably worthwhile avoiding the GC overhead.
Strangely perhaps, we want tuples to be mutable. We think that there are valid scenarios where you want to keep some data in a tuple, but be able to modify parts of it independently without overwriting the whole tuple. Also, calling methods on readonly tuple members that are themselves value types, would cause those methods to be called on a throw-away copy. This is way too expensive - it means that there may be no non-copying way of doing e.g. value-based equality on such tuples.
So we encode each tuple arity as a generic struct with mutable fields. It would be very similar to the current Tuple<...> types in the BCL, and we would actively avoid purely accidental differences. For this reason, the fields will be called
Item1
etc. Also, tuples bigger than a certain arity (probably 8) will be encoded using nesting through a field calledRest
.So we are looking at types like this:
Possibly with a constructor, some
Equals
andGetHashCode
overrides and maybe an implementation ofIEquatable<...>
andIStructurallyEquatable<...>
, but at its core exceedingly simple.In metadata, a named tuple is represented with its corresponding
ValueTuple<...>
type, plus an attribute describing the names for each position. The attribute needs to be designed to also be able to represent names in nested tuple types.The encoding means that an earlier version of C#, as well as other languages, will see the tuple members as
Item1
etc. In order to avoid breaking changes, we should probably keep recognizing those names as an alternative way of getting at any tuple's elements. To avoid confusion we should disallowItem1
etc. as names in named tuples - except, perhaps, if they occur in their right position.Deconstruction syntax
Most tuple features have a deconstruction syntax along the following lines:
If we want such a syntax there are a number of questions to ask, such as can you deconstruct into existing fields or only newly declared ones?
For now we sidestep the question by not adding a deconstruction syntax. The names make access much easier. If it turns out to be painful, we can reconsider.
Other issues
We will not consider tuples of arity zero or one - at least for now. They may be useful, especially from the perspective of generated source code, but they also come with a lot of questions.
It seems reasonable to consider other conversions, for instance implicit covariant conversions between tuples, but this too we will let lie for now.
The text was updated successfully, but these errors were encountered: