Skip to content
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

Reconsider recommended casing/naming for tuple members in corefx. #27939

Closed
CyrusNajmabadi opened this issue Nov 16, 2018 · 77 comments
Closed

Reconsider recommended casing/naming for tuple members in corefx. #27939

CyrusNajmabadi opened this issue Nov 16, 2018 · 77 comments

Comments

@CyrusNajmabadi
Copy link
Member

dotnet/corefx#26582 Added a new 'Zip' extension for IEnumerables with teh following signature:

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.

@CyrusNajmabadi
Copy link
Member Author

Tagging @stephentoub @agocke @sharwell @bartonjs

@CyrusNajmabadi
Copy link
Member Author

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!

@CyrusNajmabadi CyrusNajmabadi changed the title Determine recommended casing/naming for tuple members in corefx. Reconsider recommended casing/naming for tuple members in corefx. Nov 16, 2018
@stephentoub
Copy link
Member

stephentoub commented Nov 16, 2018

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).

@stephentoub
Copy link
Member

cc: @terrajobst

@stephentoub
Copy link
Member

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

@stephentoub
Copy link
Member

cc: @KrzysztofCwalina

@CyrusNajmabadi
Copy link
Member Author

CyrusNajmabadi commented Nov 16, 2018

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!

@stephentoub
Copy link
Member

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.

@CyrusNajmabadi
Copy link
Member Author

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!

@agocke
Copy link
Member

agocke commented Nov 16, 2018

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.

@stephentoub
Copy link
Member

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).

@agocke
Copy link
Member

agocke commented Nov 16, 2018

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

@stephentoub
Copy link
Member

stephentoub commented Nov 16, 2018

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) :)

@agocke
Copy link
Member

agocke commented Nov 16, 2018

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.

@agocke
Copy link
Member

agocke commented Nov 16, 2018

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?

@stephentoub
Copy link
Member

stephentoub commented Nov 16, 2018

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?

Yes, in that case we'd then be debating whether to instead return:

public readonly struct ZipResult<TFirst, TSecond>
{
    public ZipResult(TFirst first, TSecond second);
    public TFirst First { get; }
    public TSecond Second { get; }
}

@agocke
Copy link
Member

agocke commented Nov 16, 2018

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

@CyrusNajmabadi
Copy link
Member Author

@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!

@CyrusNajmabadi
Copy link
Member Author

CyrusNajmabadi commented Nov 17, 2018

@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.

@CyrusNajmabadi
Copy link
Member Author

CyrusNajmabadi commented Nov 17, 2018

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!

@gafter
Copy link
Member

gafter commented Nov 17, 2018

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.

@CyrusNajmabadi
Copy link
Member Author

CyrusNajmabadi commented Nov 17, 2018

@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!

@bartonjs
Copy link
Member

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).

@CyrusNajmabadi
Copy link
Member Author

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<T1,T2> 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!

@HaloFour
Copy link

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.

@HaloFour
Copy link

@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?

@DavidArno
Copy link

DavidArno commented Nov 17, 2018

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.

@orthoxerox
Copy link

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.

@alrz
Copy link

alrz commented Nov 17, 2018

@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.

@gulbanana
Copy link

gulbanana commented Nov 18, 2018

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.

@Neme12
Copy link

Neme12 commented Nov 18, 2018

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.

@GSPP
Copy link

GSPP commented Nov 18, 2018

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.

@alrz
Copy link

alrz commented Nov 18, 2018

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.

@CyrusNajmabadi
Copy link
Member Author

CyrusNajmabadi commented Nov 18, 2018

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.

@terrajobst
Copy link
Member

terrajobst commented Nov 18, 2018

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.

@omariom
Copy link
Contributor

omariom commented Nov 18, 2018

Is it a joke?

@TonyHenrique
Copy link

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

@CyrusNajmabadi
Copy link
Member Author

@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.

@DavidArno
Copy link

DavidArno commented Nov 19, 2018

@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.

@GSPP
Copy link

GSPP commented Nov 19, 2018

@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").

@gafter
Copy link
Member

gafter commented Nov 19, 2018

What's wrong with using KeyValuePair? :trollface:

@yaakov-h
Copy link
Member

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?? 😕

@jnm2
Copy link
Contributor

jnm2 commented Sep 12, 2019

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

@TonyHenrique
Copy link

I like Pascal Case. Everywhere.

@jnm2
Copy link
Contributor

jnm2 commented Sep 12, 2019

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

@terrajobst
Copy link
Member

terrajobst commented Sep 13, 2019

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

@bartonjs
Copy link
Member

  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.

@DavidArno
Copy link

@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.

@NickCraver
Copy link
Member

NickCraver commented Sep 14, 2019

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.

@danmoseley
Copy link
Member

danmoseley commented Sep 14, 2019

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.

@jcouv
Copy link
Member

jcouv commented Sep 14, 2019

@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.

@CyrusNajmabadi
Copy link
Member Author

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.

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 3.0 milestone Jan 31, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 15, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests