Runtime: Reconsider recommended casing/naming for tuple members in corefx.

Created on 16 Nov 2018  Â·  77Comments  Â·  Source: dotnet/runtime

https://github.com/dotnet/corefx/pull/26582 Added a new 'Zip' extension for IEnumerables with teh following signature:

c# public static IEnumerable<(TFirst First,TSecond Second)> Zip<TFirst, TSecond>( this IEnumerable<TFirst> first, IEnumerable<TSecond> second)

The capitalization of the tuple members names (i.e. First/Second) was somewhat surprising to me as that wasn't the naming pattern that I thought we generally followed when creating and presenting tuples, and was not the naming pattern we've used in dotnet/roslyn itself.

The intuition here was that tuples very commonly act as a shorthand way to bundle lightweight data together so they can easily be grouped and passed in and out of methods without hte heavyweight need to define an entire struct (and all the rest of the ceremony you would need there).

This naturally leads to two reasonable interpretations of things that could influence naming decisions:

  1. this is just a simpler way of dealing with a named struct. These are fields, and as such should likely be named similar to public mutable fields/properties (so presumably PascalCased).
  2. these are a collection of parameters. The tuple itself is intended to fade out of the way, and people will most naturally think of working with these as a bunch of parameters/locals. As such, they should be named similar (so presumably camelCased).

IMO, from my recollection of the design here in the LDM, we'd been pushing harder on the second position. We've put a lot of effort into making it feel very natural to just 'splat/deconstruct' tuples, pushing the positoin more that the tuple is less relevant, and it's just a bundle of variables you'll be working with locally in your methods. While it's true that that doesn't necessarily mean the tuple field names have to match the expected usage names, it feels unfortunate that these names would now have to differ in case. This goes for both consumption, where field names are being splatted into different local names, as well as construction. It's extremely natural for someone to just construct a tuple like so: (name, age), but this would, for example, now create a tuple with names that don't actually match a method that returns something like (string Name, int Age). This lack of symmetry obviously is not a problem in terms of compilation (the language allows and converts here). However, it feels problematic to me because the code is now no longer working consistently with the same names that flow in, get worked on, and flow out.

This is not a huge deal. But i think it warrants discussion as any decision made here will effectively impact all future usages of tuples across our APIs and our customers' APIs.

Thanks very much.

--

Note: additional data points. Both swift and go allow one to return multiple named values from a method/function. In both those cases, naming of those multiple-named values matches parameter naming.

area-System.Runtime

Most helpful comment

Personally I think of tuple elements as a temporary grouping of values (like method parameters) rather than being independent API elements (like fields and properties). The creator and consumer of a tuple type (that has names) can use the tuple either with or without the names. By analogy with parameter names, where the names are used at the option of the consumer, a method that returns a tuple should return them with camelCased names to (optionally) help the consumer. That's the way I've been using them and it feels right.

All 77 comments

Tagging @stephentoub @agocke @sharwell @bartonjs

Also curious what @MadsTorgersen @DustinCampbell have thought on this topic. You two have demo'd this stuff a bunch at conferences. Do either of you have a preference on how these things are most naturally thought about and named? Thanks!

While it's true that that doesn't necessarily mean the tuple field names have to match the expected usage names, it feels unfortunate that these names would now have to differ in case.

I'm not sure why that's unfortunate. It's exactly the case today when storing the value of a public field/property on a type returned from an API into a local: the former is PascalCased, the latter camelCased.

we've been pushing harder on the second position

I'm not sure who "we" is here, but we've never pushed on a position here for public .NET APIs. Folks can do what they want in the internals of their code/repos, but when we start talking about public APIs, there needs to be crisp, clear guidance, which is what we tried to do as part of this discussion.

But i think it warrants discussion as any decision made here will effectively impact all future usages of tuples across our APIs and our customers' APIs.

There are three possible outcomes here from a framework design guidelines perspective:

  1. Never use tuples as the return type of public .NET APIs. Define custom structs instead.
  2. Consider using tuples as return type of public .NET APIs (subject to a long list of constraints to be listed). Use PascalCasing when naming the elements.
  3. Consider using tuples as return type of public .NET APIs (subject to a long list of constraints to be listed). Use camelCasing when naming the elements.

I really, really, really dislike (3); I literally would rather we not ship the API at all (e.g. the new overload of Enumerable.Zip) than do that. From an API perspective, these are structs with public named property/fields, and the only reason to use a ValueTuple instead of a custom struct is to save on some effort in defining another one (and maybe there's an argument about binary size of the same generic instantiation could be reused). From that perspective, camelCasing completely breaks with every other public .NET API, where types returned from methods expose their data with PascalCasing, period.

I don't have strong preference between (1) and (2), though I generally lean towards (1).

cc: @terrajobst

ps @CyrusNajmabadi, thank you for opening the new issue. :)

cc: @KrzysztofCwalina

I'm not sure why that's unfortunate. It's exactly the case today when storing the value of a field/property into a local: the former is PascalCased, the latter camelCased.

I tried to explain that bit, but probably did a poor job. Sorry!. In this case, because the idea (to me at least) is that really is strongly just a bundle of these values. I've definitely acknowledged that these can just be thought of as nothing more than a simpler way to right a named-struct. But i think that's losing out somewhat on what tuples are really trying to get across, which is that they really can just 'fade out' and you aren't thinking of them at that level.

i.e. without question, under the covers this is a real struct. Not question about it. But tuples are intended for lots of code to help you avoid even needing to think about that. Instead of thinking about it as a struct with these public fields, it's more like "here's just these few pieces of data" and the tuple fades out.

The 'fading out bit' was a pretty intentional part of the design, and why we def invested in this like deconstruction. It wasn't necessary, and it's def not even that bad to just use the actual tuple instance value. But there was a strong enough push on the belief that the tuple itself wasn't relevant, but what was inside was what mattered the most. In this view of the world, the conception of the world is not that there's this struct with fields that you're using. It's just that you're flowing individual values along. And, just as parameters flow in as lower-cased-named data into a method, a tuple would allow you to flow lower-cased-named data out of the method.

Another way of thinking about it: if we allowed you to name a return value of a method, would we intuit that that name should match how we do parameters (since it's just named data flowing in/out of the method)? Or would we name the data that flows out with a capitalized name? I honestly don't know :) But my gut tells me we would cameCase. And since the tuple is just a bundle of those named-return-data values, camel-casing makes the most sense there to me.

I'm not sure who "we" is here, but we've never pushed on a position here for public .NET APIs.

Sorry, i meant: during teh original designs of tuples and the general philosophy around them when we did things in the LDM. But i could be totally off base here and this might have just been my own perception on how I personally thought about tuples. Would def like @MadsTorgersen @DustinCampbell to weigh in. @jcouv and @gafter were also heavily involved IIRC. i'm curious their thoughts too :)

Folks can do what they want in the internals of their code/repos, but when we start talking about public APIs, there needs to be crisp, clear guidance, which is what we tried to do as part of this discussion.

Absolutely. No question :) That's why i wanted to have this discussion. I'd love totally clear guidance here. And i'm 100% willing to accept that if corefx just cements the position of "they're pascal cased. discussion done" :)

I'm just wanting to bring up the topic because i personally feel that this warrants at least a second look and some thought as to how we expect people to use these API, and what might feel more natural within those expectations. I personally have felt that camelCasing fits best here (and the adoption across the roslyn codebase itself has seemed to back that up), and i def think ti would make sense for corefx as well. If you guys agree, great. If you think they should be PascalCased, that works for me as well :)

Thanks!

I'm just wanting to bring up the topic because i personally feel that this warrants at least a second look and some thought as to how we expect people to use these API, and what might feel more natural within those expectations.

Thanks.

Note: since there are two seemingly valid (in my mind at least) ways to think about things, i'm not sure i can add anything else helpful. There's no right/wrong answer here, so it's probably better not that you've heard my perspective to maybe re-assess and make a decision if anything needs to change here. If you land on keeping the status-quo have no problem on that.

This will hopefully prevent a ton of posts that are just going in circles :D

If you need any more info/perspective from me, don't hesitate to ask. But i'll leave it in your hands now since i don't think i have much more to add. Thanks!

The only thing that doesn't seem an option to me is

Never use tuples as the return type of public .NET APIs. Define custom structs instead.

The point of convolution is to return a tuple. I originally had the tuple unnamed. I'm not sure why we needed to get into this debate at all.

I originally had the tuple unnamed.

From a consumption standpoint, I don't know what "unnamed" means. The properties/fields would then be called Item1 and Item2, so they have names (and they're PascalCased).

C# refers to a tuple without names in the syntax as an "unnamed" tuple. It has nothing to do with property names.

Ok. But this is about how the API is consumed, and whether the thing that's returned has members:

  1. Item1/Item2
  2. First/Second
  3. first/second

I don't see how (1) is better than (2), and I don't like (3) :)

If it's unnamed in the syntax we can't have this debate as the only thing you can have is (1). The point is to not talk about this.

If I returned System.ValueTuple<T1, T2> would we be having a debate about whether or not to change the names System.ValueTuple<T1, T2>.Item1/2?

If I returned System.ValueTuple would we be having a debate about whether or not to change the names System.ValueTuple.Item1/2?

Yes, in that case we'd then be debating whether to instead return:
C# public readonly struct ZipResult<TFirst, TSecond> { public ZipResult(TFirst first, TSecond second); public TFirst First { get; } public TSecond Second { get; } }

Ha OK then I'm out. I don't care.

@terrajobst Can we continue the discussion here? Twitter just isn't a good medium for this sort of thing.

Note the above posts that call out how naming things like fields/properties as being totally reasonable. However, i feel there's another side of the coin not being considered, which has a fair amount of support from several LDM members. I would like it if this idea could at least be considered since it certainly has some merit as well.

Thanks!

@terrajobst

I disagree; it breaks symmetry with every other member access today.

This is the crux of the issue. Part of the intuition we've had when designing and presenting this i that one doesn't need to think about this as "member access" at all. Note: to be clear, i'm not saying that thinking about is member-access is wrong, or innapropriate. I'm just pointing out there are alternate intuitions.

In roslyn itself we've seen this intuition borne out, where lots of code works with tuples, but doesn't care about member accesses at all. In that regard, these aren't "members", they're just a collection of data. With that (afaict) equally valid intuition on how things work, these more naturally map to things like parameters, and are named naturally as such.

Ok. But this is about how the API is consumed, and whether the thing that's returned has members:

Right. And there's one of the core contentious points. Do we expect people to think "i am consuming a Tuple. It has members that i'm accessing."? Or do we expect people to think "i'm consuming two pieces of data, 'the first piece, and the second piece'"?

Both of these are equally valid perspectives given how this language feature has been created and how we allow it to be used. The fundamental question to me is: is one of these perspectives one we want to give higher preference to.

On a solely personal level, i find the latter perspective much more intuitive and natural. We shaped tuples very particularly to feel a certain way and to match how we've done this elsewhere in the language. We've pushed a lot on the messaging that it's just a bundle of data. That you generally don't think or care about it as this container with members.

The question here is: which perspective on things does CoreFx want to take? Note: i recognize this is a challenging topic as CoreFx is not C#. And it has to consider potentially many consumers and how they may think about these types. However, from C#'s perspective, tuples were meant to naturally fade-out, giving natural access to the actual data. And in that perspective of things, the names most naturally matching how they will be used, and having symmetry across consumption/production seems very nice.

Thanks!

Personally I think of tuple elements as a temporary grouping of values (like method parameters) rather than being independent API elements (like fields and properties). The creator and consumer of a tuple type (that has names) can use the tuple either with or without the names. By analogy with parameter names, where the names are used at the option of the consumer, a method that returns a tuple should return them with camelCased names to (optionally) help the consumer. That's the way I've been using them and it feels right.

@terrajobst

I'm responding because you keep saying more on Twitter :-) In general, I don't think there is more to say on the GitHub issue as Stephen pretty much outlined our position already.

Could this position be reconsidered based on the information present about how several LDM members think about this, and how there are multiple valid ways we can expect people to think about tuples? In particular, i think this point is very important as part of the consideration:

Right. And there's one of the core contentious points. Do we expect people to think "i am consuming a Tuple. It has members that i'm accessing."? Or do we expect people to think "i'm consuming two pieces of data, 'the first piece, and the second piece'"?
Both of these are equally valid perspectives given how this language feature has been created and how we allow it to be used. The fundamental question to me is: is one of these perspectives one we want to give higher preference to.

CoreFx here is pushing a perspective which i believe is not necessarily the one that C# wanted when we introduced this feature. I would prefer the discussion happen again to make sure all parties are ok with that, or if it would be more appropriate to change things here.

Thanks!

When deciding on the guidelines for the BCL we take into account (sometimes with more success than others) CLR languages other than C#.

NETCOBOL, for example, doesn't look like it will be able to make use of deconstruct (at least, at first glance I don't see it fitting in their language), but I can easily imagine their tooling consuming the TupleElementNamesAttribute to allow the caller to consume the ValueTuple using the aliases instead of "Item1" and "Item2". In that case, these are just field aliases, and should be named like all other fields. (I _think_ that NETCOBOL is case-sensitive on CLR invocation, but I could be wrong.)

The only place, if I understand correctly, that the TupleElementNamesAttribute matters to the C# compiler is when doing a member-access resolution, at which point it's now syntactically equivalent to a property or a field, so the names should follow property and field name guidance. (I can't seem to find the resolver syntax specification to confirm that's the only practical effect that the attribute value has)

However, from C#'s perspective, tuples were meant to naturally fade-out, giving natural access to the actual data.

The decomposed syntax mainly falls out from ValueTuple having a Deconstruct method, and any casing on the IDE replacing var with a decomposed list is just an IDE-ism, since var in that specific context is ValueTuple<T1,T2> with the resolved context of what [TupleElementNames] went with it.

As far as I know, the only real problem with this currently is that the suggestion/correction for "hey, you can deconstruct this var" copies the names as-is instead of applying the same contextual style transformation that it would apply to any other member-access to local (if there are any others).

As far as I know, the only real problem with this currently is that the suggestion/correction for "hey, you can deconstruct this var" copies the names as-is instead of applying the same contextual style transformation that it would apply to any other member-access to local (if there are any others).

The problem is the conceptual asymmetry. Even though the perspective is that the values are the same, there is a need to name them differently. This is not an issue if you don't htink of the values as being the first-class nature of the tuple, but it is if you do.

The decomposed syntax mainly falls out from ValueTuple having a Deconstruct method

I don't believe that's true. I believe tuples can always be decomposed even without a Deconstruct method. And, even with the deconstruct method, the decomposition is there to operate on the data as it was naturally expressed by the producer.

is just an IDE-ism, since var in that specific context is ValueTuple with the resolved context of what [TupleElementNames] went with it.

I'ts not really an IDE-ism. It's how hte C# language views this symbol. The symbol is a "tuple, with these element names". There is a specific encoding/decoding C# uses (with ValueTuples, and fields, and attributes), but the intent is for that to only be known insofar that it's how to interoperate with other languages. The names are specifically intended to name the data. That that data is stored at the end of the day inside fields isn't something you generally need to know or care about.

Fundamentally, C# created and encoded these to (as neal said) create a temporary grouping of values. When dealing with the values, the natural form is to just grab hte data and work with it directly (and, c# coudl have always gone with that being the only way to work with the tuple's data). But, frankly, that would likely be a little too onerous, so it's possible to both work with the tuples instance as well as just the data.

As far as I know, the only real problem

It depends on how you define 'real problem' :) As i mentioned in the first post, nothing at all breaks when using pascal-named tuple elements. You definitely can go this route. But, like neal said the other way just "feels right." So it depends on if you think it's a problem to ship a naming convention (that we'll likely not be able to ever change) that does not 'feel right' to people. IMO, that is a significant problem, and one that would be worth avoiding now so we don't end up with something we regret and which doesn't feel right to users down the line.

Thanks!

I'm with @CyrusNajmabadi and I view tuples as nothing more than a loose grouping of locals and/or parameters. The "container" exists only as a necessity in order to bridge the technical/legacy gaps in the language and runtime when passing those elements around. The tuple shouldn't be a cheap and easy replacement for a proper type with proper fields. Going with the PascalCase naming seems to imply that's exactly what it is.

@bartonjs

When deciding on the guidelines for the BCL we take into account (sometimes with more success than others) CLR languages other than C#.

I can understand this argument. To languages that don't understand tuples and see them as a container using field/property name guidelines make sense. But how many of those languages even understand tuples enough to support the element aliasing? Won't the majority of them just see Item1 and Item2? To that end, what's the point of even naming them in this particular API given that the names are just synonyms for the default field names of the tuples?

I use tuples a lot. They are one of my favourite features of C# 7. In all of the code examples from the language team I can think of, they used camelCase for the element names of those tuples. This is the convention I've therefore followed too.

As @gafter says, a tuple is a collection of values, much like locals or parameters. In fact, when the tuple syntax is used for a deconstruct, those elements are locals. Therefore using camelCase for the names does "feel right".

Under the hood, those tuple elements are then just properties of a struct. But that's an implementation detail. The tuple syntax hides that implementation detail away from the developer. By insisting that, because they are properties, the naming should follow the naming convention of properties, you are making the abstraction "leaky" by surfacing those implementation details.

Witth the API that @CyrusNajmabadi's mentions,

public static IEnumerable<(TFirst First,TSecond Second)> Zip<TFirst, TSecond>(
    this IEnumerable<TFirst> first, IEnumerable<TSecond> second)

we are effectively doing (First, Second) = (first, second). I find those PascalCase elements really jarring. They are just "wrong" when compared with the language conventions. So I agree with him: it is unfortunate that the BCL is contradicting convention in this way.

I think tuple members should match the casing of the pseudo-tuple of method parameters. That is, camelCase.

Most languages will still use ItemX names to access them and should ideally implement tuple deconstruction before or at the same time they implement tuple member names.

@stephentoub: There are three possible outcomes here from a framework design guidelines perspective:

  1. Never use tuples as the return type of public .NET APIs. Define custom structs instead.
  2. Consider using tuples as return type of public .NET APIs (subject to a long list of constraints to be listed). Use PascalCasing when naming the elements.
  3. Consider using tuples as return type of public .NET APIs (subject to a long list of constraints to be listed). Use camelCasing when naming the elements.

I think there should be a fourth point here: to never use tuple names in public APIs. Specific names are useful in specific scenarios. But when it comes to a public API, I think we should not give opinionated names (by any reasoning) to elements, rather, we could just return (TFirst, TSecond). So tuples would be playing the sole role of being a collection of values and deconstruction will be used to give specific names to each element.

Like @DavidArno, in all LDM notes I've looked at, in all presentations of tuples as a new language feature by @MadsTorgersen and @DustinCampbell I've seen that I remember, they were always camelCase. My impression has been that this was always the convention and I've followed it for that reason. The fact that we're now making it PascalCase comes to me as a big surprise.

the only reason to use a ValueTuple instead of a custom struct is to save on some effort in defining another one

@stephentoub I don't think that is the case. In the majority of cases I've used tuples so far, if they didn't exist, I wouldn't have used a custom struct. I either would have used

  • a) out parameters
  • b) instead of a list of tuples, a dictionary
  • c) simply not create this tuple-returning method at all, and instead have two methods

In fact if it weren't for tuples, I don't think anyone would have proposed this API at all. We'd just always use the existing Zip method with a Func so that we can create whatever container we prefer (often that's actually an anonymous type). So it's just not true that this is a replacement for a custom struct with two properties.

From an API perspective, these are structs with public named property/fields

  • From an "API" perspective, it's returning ValueTuple<TFirst, TSecond> which is a struct with pascal cased fields, so everything should be OK.
  • From a C# perspective, it's not returning a struct. It's returning a tuple type.

So it's just not true that this is a replacement for a custom struct with two properties.

out parameters aren't relevant here. You can't store them in the Current of an enumerator.

A dictionary isn't relevant here. It would be super expensive in comparison, would require indexing by who knows what to access the results, etc.

So assuming this method exists, I don't see how my statement isn't true.

If we want to remove this method, I'm fine with that, as I stated in one of the multiple other threads on which this discussion had been going. But that doesn't speak to the larger question to which I was speaking.

I think there should be a fourth point here: to never use tuple names in public APIs

That is another option, yes, thanks, I should have included it. I'm personally not a fan of it, though. Why force a developer to figure out what Item1 and Item2 mean when more descriptive names are possible?

This helps to highlight why I believe camelCasing is problematic, though. If we returned a struct, the names would be PascalCased. If we returned ValueTuple, the names would be PascalCased. The .NET APIs have always been defined such that dotting off of the value returned from a method gives you names PascalCased. We should not break from that now. Developers can name their locals whatever they want. The IDE can expose refactorings however it wants. Methods on one's own code can use whatever naming the developers wants. But when it comes to public .NET APIs, these have always been PascalCased and should continue to be so.

The only place, if I understand correctly, that the TupleElementNamesAttribute matters to the C# compiler is when doing a member-access resolution, at which point it's now syntactically equivalent to a property or a field, so the names should follow property and field name guidance.

@bartonjs This will no longer be true in C# 8.0 with recursive patterns. You'll be able to use the tuple elements names in a deconstruct pattern:
```c#
var foo = (first: 0, second: 0);

switch (foo)
{
case (first: 1, second: 1):
Console.WriteLine();
break;
}
```
(https://sharplab.io/#v2:EYLgZgpghgLgrgJwgZwLRIMaOQSwG4SoAOsMECAdsgD4ACADAAS0CMA3ALABQtAzMwCZGAYW4BvboynN+tACyMAsgAoAlJOkSu0nYzxQEjMAHtjjALyNlYHAmQwQjegBpGyCBmMUAJo/qrObV0pDWDkAHccGAwACysTY3Ug4K1g4Iwod3jbe0cWV3dPHzzVEFC04NYATjVAiorgJCgAazq0gF9yzq52oA===)

It would feel very odd for them to be pascal cased here, since this is supposed to resemble an argument list, and it's syntactically equivalent to having a Deconstruct method with these as parameter names.

out parameters aren't relevant here. You can't store them in the Current of an enumerator.

A dictionary isn't relevant here. It would be super expensive in comparison, would require indexing by who knows what to access the results, etc.

My first 2 examples we not related to this particular API but as a counter-argument to your point saying that the only reason to use tuples is as a replacement for a custom struct (in general). I explained in a second paragraph what I think would have been the case for this API if it weren't for tuples - I don't think it would exist, as opposed to being added with a custom struct.

Personally I think of tuple elements as a temporary grouping of values (like method parameters) rather than being independent API elements (like fields and properties). The creator and consumer of a tuple type (that has names) can use the tuple either with or without the names. By analogy with parameter names, where the names are used at the option of the consumer, a method that returns a tuple should return them with camelCased names to (optionally) help the consumer. That's the way I've been using them and it feels right.

Came here to say something almost identical to this. I feel very strongly that pascal-casing would unnecessarily break a valuable parallel. The incoming argument list uses camel-case names, and the matching outgoing variables should also be camel-cased even when the tuple type is used as a generic type parameter.

When I settled on camel case all the way, it seemed to me from reading the LDM notes and discussions that the symmetry between tuple types and parameter lists is essential to keep as far as it can go, and that the similarity between tuple types and structs is incidental and not important.

The .NET APIs have always been defined such that dotting off of the value returned from a method gives you names PascalCased. We should not break from that now.

I think that mirroring named argument lists is more important than ensuring that the letter following a dot is capitalized. Once you decide that camel-casing after a dot is okay, it's no worse in my opinion than using camel-case to specify parameter names or private field names. It's just another such scenario: the bag of variables.

NETCOBOL, for example, doesn't look like it will be able to make use of deconstruct (at least, at first glance I don't see it fitting in their language), but I can easily imagine their tooling consuming the TupleElementNamesAttribute to allow the caller to consume the ValueTuple using the aliases instead of "Item1" and "Item2".

It's not as simple as adding "tooling consuming the TupleElementNamesAttribute". It would require a language change for them to support that.

as a counter-argument to your point saying that the only reason to use tuples is as a replacement for a custom struct (in general)

That is not what I said. I said "the only reason to use a ValueTuple instead of a custom struct"... there's a big difference.

Sorry. But that still assumes that if it weren't for tuples, the solution would have been a custom struct.

That is another option, yes, thanks, I should have included it. I'm personally not a fan of it, though. Why force a developer to figure out what Item1 and Item2 mean when more descriptive names are possible?

Fwiw if the names are 'first' and'second', I don't think that that is any more descriptive than Item1 and Item2.

Here's what is strange for me. Languages that don't understand tuples will havea totally fine experience. They will see these as a ValueTuple and will be able to access the members normally.

This is solely about what goes in the attributes containing the tuple names. These attributes were created by C#/VB to encode the names expected to be authored by C#/VB devs (or other languages that want to interoperate with them). Despite the, and despite the strong arguments from the C#side as to why these should be camel case (as we've been presenting since incredibly early on), corefx is going with PascalCase even though that seems worse for the primary consumers here. I'm personally not really seeing who is helped by this. But i definitely seea large group that is hurt by it.

The tuplenamesattribute exists for those languages that want to think about this as a grouping of data. The primary two languages that support this syntactically project that grouping of data with the same syntax as parameters. So the banking should follow the preference we have for those languages already. It should follow parameters because that's how your stuff naturally is projected.

Languages that don't care about projecting will be fine. They have the tuple instance and the fields. This is just about the right names for languages that do project. C# and VB have been pretty clear from the beginning the right way, so it seems odd that corefx would do differently given these will be like 95%+ of all production and consumption of Named tuples.

I think there should be a fourth point here: to never use tuple names in public APIs. Specific names are useful in specific scenarios. But when it comes to a public API, I think we should not give opinionated names (by any reasoning) to elements

I actually agree with this. A major reason for this is that these APIs aren't useful to an end language unless they understand this naming-attribute. So, if you returned a (string firstName, string? middleName, string lastName), that would be projected to any other language (including prior versions of C#) as just ValueTuple<string, string, string>. Meaning anyone using those languages just sees Item1/Item2/Item3, with no additional semantic information to know what is what. Worse if this is a large tuple, where you would have to do things like .Rest.Item3 and so on.

If a ValueTuple was to be used in an API, i think it would only be for cases where you wanted to just have an ordered collection of data, with the semantics clearly defined by that alone. i.e. no names at all. So, for example, for 'Zip' things would be generally apparent given that you are passing in two sequences, and getting the tuples of paired elements from each. But beyond that, very few APIs would actually be using tuples since the names would be an important part of the contract being exposed.

I think that mirroring named argument lists is more important

Interesting point on top of this: 'NamedArgs/Args' is how C#/Roslyn actually represent tuples:

https://github.com/dotnet/roslyn/blob/578ce2e8cca3a3ebad7895c5e46855db5a405967/src/Compilers/CSharp/Portable/Syntax/Syntax.xml#L358-L362

The symmetry here (as mentioned by Mads/Neal/Julien/Myself/Others) is very intentional and has been a core idea that has been used to help shape and direct this and ancillary features. I think it was always taken for granted that these would be aligned. After all, that's how C#/Roslyn/LDM have always been messaging these guys. So i think this decision has come as a bit of a surprise, especially since it doesn't seem to have been run by the relevant stakeholders on the language side.

C# and VB have been pretty clear from the beginning the right way, so it seems odd that corefx would do differently given these will be like 95%+ of all production and consumption of Named tuples.

I have to say, I find this and similar arguments incredibly frustrating. Over two years ago, before tuple support shipped or was broadly evangelized, those of us representing corefx and framework design guidelines voiced strong preferences for PascalCasing, which then intentionally or not was apparently ignored as far as evangelizing the camelCasing approach. To now say that corefx is at odds and doing it differently feels like a slap in the face.

In any event, I've heard all the arguments, I've written sample code exploring the various approaches, and I continue to believe that PascalCasing is the right answer for any publicly exposed .NET APIs. I assume those with strong differing opinions have done the same and will remain similarly unswayed. We're all also just rehashing the same arguments in different ways over and over and over, so I'm not expecting there to be any additional arguments on either side that will move the needle or convince anyone away from their existing position.

Further, there are plenty of other reasons not to use tuples in public .NET APIs, e.g.

  • They don't version / evolve well if, for example, you want to return an additional piece of data in the future.
  • They don't document well or exhibit good support for IntelliSense and the like.
  • The don't provide the ability to specialize/optimize how the data is stored.

etc. That all applies equally to when just using ValueTuple directly, but there's now the further disagreement on how named tuples would surface, and I expect either a camelCasing or PascalCasing choice here is just going to anger a non-trivial subset of .NET developers.

My original stance before this Enumerable.Zip API was proposed was that we should simply not expose tuples in .NET APIs. At this point, I'm back to that stance. I believe it should be a general framework design guideline to avoid tuples, named or otherwise, and we should remove the newly added Enumerable.Zip overload, which adds very little value anyway (you can easily do it yourself with Enumerable.Zip(first, second, (f,s) => (f,s))).

Obviously everyone is welcome to continue discussing and to disagree with me. But I'm going to refrain from responding to this thread further, as I don't have anything of value further to add beyond what I've already stated, and I'm tired and don't want to debate tangents. I'd also appreciate it if people stopped looking up my email address and sending me one-off emails directly to tell me why I'm wrong.

I have to say, I find this and similar arguments incredibly frustrating. Over two years ago, before tuple support shipped or was broadly evangelized, those of us representing corefx and framework design guidelines voiced strong preferences for PascalCasing, which then intentionally or not was apparently ignored as far as evangelizing the camelCasing approach.

I personally don't remember any of that happening in any LDM meetings. I can see there was an email thread on the topic that i was not included on unfortunately :-/

My concern here is simply that it's unclear to me why this is being done now. THe LDM had strong reasons to push for the camelCase approach (as has been outlined ad nauseum above). This has been how things have been presented and messaged, and it seems as if there isn't any problem with that. Even our own direct experiences using this first hand have borne out that this approach "feels right".

So, my overarching question is: why go a different direction now? It just feels like it would be cause more of a rift given that whatever corefx chooses will certainly become how people do things. This convo even started because of @sharwell's discussion on how tooling would now need to follow the Corefx convention instead of the C# convention that has been messages. This seems like a decision that puts corefx at odds with C# here, even though C# designed these things this was very intentionally. As i mentioned above, the symmetry is not an accident. It's a core part of how tuples have been thought about, and it has been woven into many of the designs and expected coding patterns around them. They are very specifically not intended to be thought about as members, but rather as a loose collection of data that can be passed around (similar to how parametres are a loose collection of data passed into something).

This naming isn't inconsequential. It has a large impact on how we want people to think about these guys, and it's definitely the case that hte langauge feature was designed very intentionally so that thinking about htem as members was not the primary way to think about things.

My original stance before this Enumerable.Zip API was proposed was that we should simply not expose tuples in .NET APIs. At this point, I'm back to that stance.

and we should remove the newly added Enumerable.Zip overload, which adds very little value anyway

That would be great. What would it take to make forward progress on that position? If we can do htat, it gets rid of the whole debate entirely, and doesn't potentially bifurcate the userbase over those following the C# POV here, and those following the Corefx POV.

Should we open another bug? Or is this the right bug to handle it?

Should we open another bug? Or is this the right bug to handle it?

@stephentoub I know you said you were bowing out. But could you just answer this question as a matter of process? I'm not familiar with how corefx prefers doing things. So i defer to you on this. Thanks!

But could you just answer this question as a matter of process?

This issue is fine. It's marked as api-ready-for-review so it'll show up in @terrajobst's queries, and it's in the 3.0 milestone.

Would it satisfy both camps to just nix the names on the tuple elements? As mentioned they don't add anything of value in this particular case.

This issue is fine. It's marked as api-ready-for-review so it'll show up in @terrajobst's queries, and it's in the 3.0 milestone.

Thanks much! Sorry for any unpleasantness here :-/

Would it satisfy both camps to just nix the names on the tuple elements? As mentioned they don't add anything of value in this particular case.

I'd be ok with that as well. Effectively my primary concern is avoiding a strict rule being created right now about the naming of tuple elements. I'll leave it to @terrajobst and the corefx review to decide which of any paths forward they want to take.

I am personally not bothered in the slightest by the fact that camel case names would appear in member access. I think it helps call out that there's no concept of encapsulation (in that sense you could even draw a parallel to private fields being camel case) and what i'm working with is just a bag of variables (as opposed to being a concept of its own), and that I should probably consider deconstructing it.

the code examples from the language team I can think of, they used camelCase

The samples in the official C# language documentation for Tuples are using a mix of camelCase and PascalCase:

https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-7#tuples
https://docs.microsoft.com/en-us/dotnet/csharp/tuples#assignment-and-tuples
https://msdn.microsoft.com/en-us/magazine/mt493248.aspx

The last article captures the state of the world accurately by saying "However, the convention for tuple item names isn’t well-defined". And then continues with recommendation: "Consider using PascalCase for all tuple item names.". The same recommendation is in the last edition of the classic "Essential C#" book written by the same author.

Further, there are plenty of other reasons not to use tuples in public .NET APIs

Another reason is performance. Tuples have large footprint at runtime compared to what they do. ValueTuple type implements number of non-generic and generic interfaces that all need to be created even if they are not actually used (in the current .NET implementations at least). It is probably ok to use tuples in Linq that has large runtime footprint already, but I do not think we would want to start using them a lot in the core APIs.

I think we should separate naming from whether or not to use tuples as they are orthogonal decisions. It seems silly to me that we would not use tuples only because we can't agree on names.

  1. Having tuples in the framework. Our current position on tuples is at least AVOID, due to the versioning constraints they have. Deconstruct methods are fine though, as we can add overloads later. So far, I haven't seen compelling reason for the use of tuples in the framework; they make sense for Zip and they would make sense for an (hypothetical) API like (int, T) SelectWithIndex<T>(this IEnumerable<T> source). But neither example is a very compelling API to begin with.

  2. Naming. I feel like that we have debated the pros and cons in this thread to death. In the end, I find myself in the same position as @stephentoub that I find the arguments for camel casing rather unconvincing. The argument that tuples mirror parameter lists could equally be applied to any constructor whose parameter value matches the property/field it initializes, yet we don't. On the other hand I find the argument for PascalCasing quite convincing: the names are aliases for the fields and thus appear in member access. So regardless of personal preference, I'd argue that PascalCasing is objectively more consistent with the existing naming guidelines than camelCasing would be.

What’s wrong with the idea of eschewing tuples in public API because they’re difficult to name consistently? The naming of API elements is important. Tuples are a great way to pass data around without ceremony, but public API design is all about selecting the appropriate ceremony.

The argument that tuples mirror parameter lists could equally be applied to any constructor whose parameter value matches the property/field it initializes, yet we don't.

No, it couldn't. The argument is that tuples syntactically mirror parameter and argument lists, whereas constructor parameters and field declarations are unrelated.

As @CyrusNajmabadi said, tuple expressions actually consist of an argument list in the syntax - it's the same syntax node and there is no difference between a tuple expression and an argument list of an invocation.

The intensity of this discussion shows that tuples should not be in any public framework API at this point. Once they are in they will be in forever. Design mistakes cannot be fixed.

Adding names later wouldn't be a breaking change. I think we could go with unnamed tuples and see how the client code would ever use these. I'd expect it to be deconstructed to locals most of the time, but who knows.

Having tuples in the framework. Our current position on tuples is at least AVOID, due to the versioning constraints they have. Deconstruct methods are fine though, as we can add overloads later. So far, I haven't seen compelling reason for the use of tuples in the framework; they make sense for Zip and they would make sense for an (hypothetical) API like (int, T) SelectWithIndex(this IEnumerable source). But neither example is a very compelling API to begin with.

Agreed. It seems like a good way forward is to just remove these.

Naming. I feel like that we have debated the pros and cons in this thread to death. In the end, I find myself in the same position as @stephentoub that I find the arguments for camel casing rather unconvincing. The argument that tuples mirror parameter lists could equally be applied to any constructor whose parameter value matches the property/field it initializes, yet we don't. On the other hand I find the argument for PascalCasing quite convincing: the names are aliases for the fields and thus appear in member access.

This is the underlying representation. But it's not the language representation. And the names exist to support the language representation. Tuples were designed so that you could work with teh underlying representation (after all, you could be non-C#, or C# pre-tuples). However, the naming part was to allow C#/VB to rountrip these element names. The language representation isn't that htey're fields. They could honestly be anything. Instead, they're jut named elements that are semantically and syntactically bundled together to look like they're parameters. By naming things in this manner, it goes against the symmetry and intuition that the language features were designed against.

So regardless of personal preference, I'd argue that PascalCasing is objectively more consistent with the existing naming guidelines than camelCasing would be.

THe core problem here is one of perspective, and if you think the names map to the underlying representation (i.e. fields) or if you think the names map to the language projection. IMO, the names were specifically designed for exactly the latter purpose. So, while they're used to map fields in that regard, the names are "objectively** more consistent" in that they map them to parameter-like data.

It's very intentional that we didn't design tuples to look like { int Field1; string Field2; } which would look and feel much more like actual structs/types with fields. If we had thought of them as analogous to fields, we would have gone with a very different syntactic look and feel.

Instead, we intentionally designed them to project into the language as (int param1, int param2). Syntactically, we've pushed the mentality that these are like parameter lists, and the naming has been in line with that. That this is projected into metadata/IL as a struct+fields is not the primary way they're intended to be thought about. Given that, i think it's objectively** better that the names (which were created to support this C#/VB perspective) match the C#/VB POV on what these guys are.

Thanks!

--

** Note: i personally don't think it's objective. I think it's rather subjective TBH :) There are two perspectives on what these thigns are, and the BCL straddles both worlds. It just that i think the name-attribute was meant to project C#'s perspective here, so it would be subjectively better to align to that rather than IL/metadata's perspective.

IMO, the names were specifically designed for exactly the latter purpose.

I don't believe that's the entire purpose for their existence though. Mads and I had a conversation about this early on and we concluded that having names is a pre-req to use them in public APIs ever.

The core problem here is one of perspective, and if you think the names map to the underlying representation (i.e. fields) or if you think the names map to the language projection.

As a framework designer, it's a our responsibility to allow it to be used by a variety of languages and tools. In principle, there is nothing C# specific about tuples or names. Other languages, such as F# or even Cobol.NET could use the names as they are recorded in metadata.

Quite frankly I think the conclusion on this thread is sadly that we can't agree on the bare minimum that would allow these types to be used in any public APIs. I also find myself agreeing with @stephentoub that the tone and way this discussion has evolved is rather frustrating. Since we have bigger problems we need to solve right now I think the conclusion here is:

DO NOT use tuples in public APIs.

That means their use and naming are private implementation details so anyone is free to make up their mind on how they want to name them. Personally, I feel this outcome is rather unfortunate because it's virtually guaranteed to create a mess, but hey.

Is it a joke?

I dislike camelCase, everywhere.
If I could choose I would write everything related to objects/variables in PascalCase, even JavaScript code...

@TonyHenrique

I dislike camelCase, everywhere.

camelCase is used extensively in .Net. Most importantly, it's used for locals/parameters. Which are the types of data that C#/VB tuples were designed to represent an aggregation of. C#/VB specifically picked syntax that mirrors these constructs intentionally as the intuition was to think about them as if they were the same. i.e. you could just 'pick up' your parameter list and make it into tuple and then pass that along (both into and out of methods). As such, the C# perspective on these guys is that they would naturally follow how parameters/locals are named. And those are widely written by nearly the entire ecosystem as camelCased.

If .net switched to making parameters PascalCased, then that would follow for tuples as well.

@terrajobst,

I'm disappointed that your conclusion is "DO NOT use tuples in public APIs". This is not good advice in my view.

It's a great shame that the various Microsoft teams cannot agree a common strategy to naming conventions for tuple elements. You are right that it will create a mess. Many, myself included, will just ignore your conclusion and carry on using tuples in public APIs when a tuple is the right thing to use. But there will be no accepted convention that we can follow for their names, so both camelCase and PascalCase will be used by different people for their APIs.

@DavidArno both naming choices have pretty big downsides. It's not necessarily a matter of just agreeing, making peace and going with it. This is not just a social problem. There is simply no viable alternative.

I have faced the same naming conundrum in my own code. I also concluded that tuples should only be used in internal places (for some vague, intuitive notion of "internal").

What's wrong with using KeyValuePair? :trollface:

In principle, there is nothing C# specific about tuples or names. Other languages, such as F# or even Cobol.NET could use the names as they are recorded in metadata.

Well, named tuples are a language feature, so they would be specific to the language that defines them, no?

AFAICT:

  • C# defines VTs as transient collections of parameters/locals and thus they deserve camelCasing.
  • F# seems to camelCase them in the documentation I've found, despite PascalCasing members in general.
  • VB documentation seems to mix between camelCasing and PascalCasing even within the same articles.

I can understand a "do not use names" or "do not use VT" rule for reasons to do with the BCL having to support multiple languages... but I don't see any _language_ pushing for PascalCased(-only) tuple names.

It appears to be only BCL developers, which puts CoreFX at odds with _all it's major consumers_... for ideological reasons not adopted by any language design team?? 😕

Looks like precedent has now been set that tuple element names should be pascal-cased when used as a return type: dotnet/corefx#35595

I like Pascal Case. Everywhere.

I take it we shouldn't call you @​tonyHenrique then =)

Yes,, @bartonjs is working on guidelines for tuples, it basically is:

  1. Prefer custom structs/classes because they can be evolved
  2. If you use tuples, use ValueTuple<>
  3. Name your tuple elements using PascalCase
  1. If you use tuples, use ValueTuple<>

It's more "If you use tuples, use named tuples." (which happens to mean ValueTuple + attributes). Returning (unnamed) ValueTuple is a no-no.

@terrajobst,

It’s extremely disappointing that the BCL team are in any way trying to offer guidelines on a language feature. All three guidelines are unhelpful and miss the point of tuples completely.

Whether to use a tuple or custom struct/class is both subjective and dependent on many factors. A simple “prefer one over the other” guideline is a nonsense.

If you use tuples, use the () tuple notation. What the underlying type is for that notation is not relevant to 99% of use cases and so should not be mentioned in any guidelines.

And as this thread shows, the BCL desire to
name tuple elements using PascalCase runs against convention and just creates unnecessary conflict. At best you should use camelCase, failing that just keep quiet on the subject and leave folk to choose.

There must be guidance for their usage in the BCL to keep the BCL APIs consistent. That is the BCL team's job. This a naming convention, the BCL already defines many of them (his is nothing new) to keep the BCL consistent.

It should be noted that "using PascalCase runs against convention" is not a given - it may go against _your_ convention but as this thread shows there isn't one. For example, we use Pascal internally. There needs to be a defined convention in the BCL, which is why this was a blocker to exposing APIs using them.

Try to imagine for a moment if _any_ other external API in the BCL _didn't_ have a naming convention and where we'd be.

It’s extremely disappointing that the BCL team are in any way trying to offer guidelines on a language feature

@davidarno it has always been part of the role of the .NET Libraries team (going back to the original Framework Design Guidelines) in collaboration with others such as the language designers and the community, to provide guidelines intended to help create better, more consistent.NET libraries.

@DavidArno I can attest that this is the result of a joint discussion of representatives within the .NET team, including language/compiler and BCL. Like many such decisions, it is not easy, but I think it is ultimately helpful to have an overall agreement and consistent approach. So even though we didn't land on my initially preferred approach, I'm glad we landed on a decision and I'm satisfied with it.

I agree with @jcouv . This is definitely not my preferred approach. However, my major desire was that there be a joint conversation with the relevant stakeholders on the matter. That appears to have been done. And, like with many decisions, not everyone would be satisfied. However, everyone got to have their voice heard and the decision was made understanding the ramifications. That's good enough for me.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jzabroski picture jzabroski  Â·  3Comments

v0l picture v0l  Â·  3Comments

chunseoklee picture chunseoklee  Â·  3Comments

noahfalk picture noahfalk  Â·  3Comments

btecu picture btecu  Â·  3Comments