Closed Unknown6656 closed 4 years ago
I guess that https://github.com/dotnet/csharplang/issues/3526 would be the relevant discussion, however, I just wanted to raise the issue again as I feel that such important syntax introductions deserve more community attention/interaction.
I'd like to ask the LDT to consider changing the current record-syntax to record class MyRecord(...) instead of record MyRecord(...).
The language design group explicitly considered this (and a few other options) but settled on record X(...)
as our preferred final point.
Furthermore, Visual Studio displays the following when hovering over the record's identifier:
This is being fixed. The VS display does not inform the language. The language informs the VS display. You'll note that with any new lang feature we create that passes through metadta teh VS display will often need to be updated to match it. i.e. we don't display NRT types as [Null...] string s
, we do it as string? s
Provide some syntactic symmetry to the record struct proposal
We considered this, but didn't find the symmetry compelling. Furthermore, it may not be record struct
(or struct record
as i would prefer). It may just be something like value record
. We prefer the idea of record
being a concept that has a simple way of stating it that suffices for the vast majority of cases. And, in the future, there may be a way to take that concept and say and i woudl like the value-type form of it
. It's not a struct that's record. It's a record that is a stack-alloc'ed type to me.
It would have some symmetry with primary constructors,
I don't see this.
Less ambiguity with the traditional method syntax (at least Visual Studio 16.7-prev.6 highlights the record's name as if it were a regular method)
We considered this but found this an uncompelling argument. Both because we actually do not envision any realistic scenario where this woudl matter, and also because you only get this through an explicit upgrade step where you choose to use these versions of hte runtime/language.
Provide some kind of syntax design freedom: We could still drop the class-keyword in a later iteration of C# .... the other way around would be a breaking change.
I don't see how this would be the case. We coudl always add record class
in the future if we really felt it was necessarily for explicitly stating that your record is a reference type.
The alternative would be to change VS' tooltips and syntax highlighting, in order to hide the class-implementation of the record-concept from the developer.
This is already happening.
I hope that there is still enough time left before November (the month in which C#9 will be released to the larger public).
There is no motivation from anyone i'm aware of to change this design. The points you brought up were all considered, discussed, evaluated, and found to be insufficient to outweigh the benefits we saw with the record Foo
syntax.
@CyrusNajmabadi
I understand that this horse is hamburger by now but it feels really weird to carve out new flavors of types based on these features, especially since these "types" are nothing more that a combination of other features tacked onto existing types (to the extent that recent notes specifically stated that by C# 10.0 all of these features can be applied to existing classes). record
by itself doesn't convey anything, and neither does value
. And value record
? It feels like desperately trying to name it something distinct to make it stand out. But it doesn't stand out, and in 5 years they'll just be weird names.
In Java this makes more sense because Java only has one kind of type that you can define. When it gets a second kind of type that will always be conveyed by a modifier, but they thought that value
was meaningless and dropped it in favor of inline
.
And the decision to omit class
and make it a third type was also at the same time that members of the LDM hoped that structs could just be records, but that's now known to not be possible due to the additional opinions that records bring.
i agree - especially considering the decision for #3732 - that "records" are Not a Type, or even something that are supposed to be detectable via reflection by any means.
declaring something to be a "record" is just a way to autogenerate a specific code contract for a class. - in some ways, it's just an ultra fancy interface with some default implementations.
The mental model for interacting with types is not "records", "classes", "structs", It is just "classes","structs".
If you had to teach someone what a record was, could you do so without using the word "class"?
If you had to teach someone what a record was, could you do so without using the word "class"?
Once we get struct-based records, that will be easy... 😉
I agree with the OP: dropping the word class
is clearly a bad decision. The feeling of déjà vu is strong here and history will likely show it was a decision on par with in
, ie we shouted loudly that it was wrong, they ignored us and we were proven right. But as we all know, once the language team have made up their mind they are not likely to listen to voices here suggesting changing that collective mind. So it is what it is: we'll just have to live with their decision and we might as well move on to thinking about other things. 🤷
I've changed my mind and think it's the right decision. We want using a record to be as short as possible, and so we want fewer modifiers. As ever I think this is a case of http://gafter.blogspot.com/2017/06/making-new-language-features-stand-out.html?m=1
@CyrusNajmabadi Will you consider a special syntax for struct-based records too? where do you draw the line?
As ever I think this is a case of...
Yes, dropping the word class
literally is an example of what @gafter writes about there. Nice link.
Well I do not really agree on the 'shortness' issue: You probably only have to type pub[TAB]re[TAB]cl[TAB] to produce public record class
.
Also, I think that shaving of 5 alphanumeric characters and one space ("class
") is not with giving up all issues regarding symmetry (@CyrusNajmabadi).
Furthermore, declaring a record is a modification to the existing class type - therefore I strongly feel that record
should be employed as a modification and not a substitution to a class declaration.
@HaloFour
I understand that this horse is hamburger by now
Damn! I have to remember that expression. +1
@YairHalberstadt Thank you for the URL!
@CyrusNajmabadi:
I have read all your arguments and thank you for answering me in such a detailed way! I do not agree with them (especially on the LDT's stance facing syntactic symmetry!!), but I respect the LDT's choices.
However, I do get the impression that decision-making process for language design is rather intransparent for non-Microsoft GitHub community members. Do not take it the wrong way - we are thankful for all elaborate LDT meeting notes! But I do somehow get the superficial impression, that community members here do not have a great access to voting on specific design issues.
(Maybe that is a good thing to prevent stuff like #993 ..... If that got through, we would be discussing record
vs record class
vs 记录类
now 😉)
@CyrusNajmabadi Will you consider a special syntax for struct-based records too? where do you draw the line?
I don't know what "line" you're referring to.
Also, I think that shaving of 5 alphanumeric characters and one space (" class") is not with giving up all issues regarding symmetry
I don't think you get the argument i'm making. I'm not saying: record
is better than record class
(or class record
) because you don't have to type class
. I'm saying: i don't think that i want class
here at all.
ie we shouted loudly that it was wrong, they ignored us and we were proven right
You've shouted loudly about many things @DavidArno . Being loud doesn't make you right. It just means you're loud. Given how many times you've complained in this manner and things have not been a problem, i don't see any reason to feel that this will be a different situation.
I'm saying: i don't think that i want
class
here at all.
Ok, but why do you want to get rid of class
if not for shortness reasons?
Do you plan (in the long run) to replace nearly all classes with records? (except when mutability is absolutely required)
@Unknown6656
Do you plan (in the long run) to replace nearly all classes with records? (except when mutability is absolutely required)
Records aren't immutable. Primary constructor parameters by default emit an init
-only property, but you can override that by defining the property yourself. For nominal records it's up to you to make the fields/properties readonly.
@CyrusNajmabadi
The only insight we have into the conversation that took place is what is written in the meeting notes. All that does is effectively lay out the two options, list all of the problems with public record
(including "Normally we would never consider this kind of change,") and then shrug and say that the decision is public record
. The closest thing we have to a reason is that there is value in being able to consider a record as a type of it's own, but not why you would want to do that. I dare say I think I know what Andy thought of this decision.
Eric Lippert was pretty clear on this subject. There are three kinds of types: copy-by-reference, copy-by-value and references. Classes are copy-by-reference and structs are copy-by-value. Records are firmly copy-by-reference, therefore they are classes. Their mutability makes it impossible to even attempt to paper over this reality. Records in C# 10.0 might be able to be copy-by-value, therefore they are structs (or, more accurately, value types, but that's what the term "struct" means in C#). Introducing value identity semantics to a copy-by-reference type doesn't make them a different kind of type; people have been making these kinds of types in C# since 1.0.
@HaloFour
Records are firmly copy-by-reference, therefore they are classes.
Delegates, interfaces and arrays are also copy-by-reference. Does that mean they are classes?
Records in C# 10.0 might be able to be copy-by-value, therefore they are structs
Are enum
s also struct
s?
@svick
Delegates, interfaces and arrays are also copy-by-reference. Does that mean they are classes?
Yes.
Are
enum
s alsostructs
?
Yes. Just like System.Int32
is a struct.
I get your point, though. There is precedence for other copy-by-reference types in the C# language (and the BCL). But I'd argue that those types are special in very different ways. Records are just an aggregate of smaller features.
Delegates, interfaces and arrays are also copy-by-reference. Does that mean they are classes?
Delegates: yes, of course. Arrays: yes. Interfaces: One could argue, but in essence, yes they are.
Are
enum
s alsostruct
s?
Yep. Enums are constrained to (a subset) unmanaged struct types and they're copied by value.
(Oh nevermind, @HaloFour answered all these already.)
Ok, but why do you want to get rid of class
I'm don't want to get rid of class
. I'm saying there's nothing that makes me want to have class
in the first place.
You are approaching this as if I am going and explicitly subtracting this. I'm saying that I'm starting from a position where were adding this new feature, and giving it its own appropriate syntax, and that syntax has no need for the class
keyword (just as it has no need for any other keywords).
Do you plan (in the long run) to replace nearly all classes with records?
No. I plan to use records for cases where records are a suitable solution. And I will continue using classes for where they are appropriate.
(except when mutability is absolutely required)
I don't know what mutability works have to do with things...
With enums you don't write enum struct
. With delegates you don't write delegate class
. We could have gone that route originally, but always felt a single keyword was appropriate, even if one sort of type shared a lot in common with another. We don't think of the world as "all structs and all classes, with a modifier to state what variant of them were currently creating". I don't see why we'd start changing things up now for this new feature.
You are approaching this as if I am going and explicitly subtracting this. I'm saying that I'm starting from a position where were adding this new feature, and giving it its own appropriate syntax, and that syntax has no need for the class keyword
Well that was kinda getting the impression that the LDT wants to get rid of the class
keyword. I now do see your arguments.
I guess that I could live with the current record syntax .... however, I still think that the Github community did not really get a chance to vote on the syntax as might be the case with the F# language design process (This is however a kind of meta-issue).
With enums you don't write enum struct. With delegates you don't write delegate class.
Fair point. You are right on that one.
I don't know what mutability works have to do with things...
I was wrongly assuming that records will be immutable.
including "Normally we would never consider this kind of change,") and then shrug and say that the decision is public record
The reason we normally wouldn't do this was entirely about introducing a pure syntactic ambiguity. We've rarely ever had that in the language. However, there is precedent for it as both c#1 had pure ambiguities, and future versions added them as well.
Our default position is to not add them. However, like we have I'm the past, we do it if we think the value is high and the risk is low. That's the case here. The chance of people hitting this is extremely low, and we think trying to workaround that car just leads to a much clunkier and less desirable syntax.
I guess that I could live with the current record syntax .... however, I still think that the Github community did not really get a chance to vote on the syntax
Voting by GitHub members is not part of the language design process. :-)
Voting by GitHub members is not part of the language design process. :-)
I know that, but I think that this is a bit sad ..... however, I can understand the reasons for not letting the GitHub community vote.
Well that was kinda getting the impression that the LDT wants to get rid of the class keyword.
We had an initial prototype that used that keyword to sidestep the pure syntactic ambiguity of the desired syntax. But we discussed things and said we'd rather have the ambiguity, but get the syntax we like than have no ambiguity but clunkier syntax. That's why it came up. :-)
I know that, but I think that this is a bit sad ..... however, I can understand the reasons for not letting the GitHub community vote.
Noted. Maybe this will change in the future :-)
@CyrusNajmabadi
With enums you don't write
enum struct
. With delegates you don't writedelegate class
.
Both are true, but you also can't create either of those types out of a struct or a class as you can with records. You also don't have two flavors of each, copy-by-reference and copy-by-value, as you will with records. A record is nothing more than the accrual of a bunch of orthogonal opinions applied to a class, until it's no longer a class?
We had an initial prototype that used that keyword to sidestep the pure syntactic ambiguity of the desired syntax. But we discussed things and said we'd rather have the ambiguity, but get the syntax we like than have no ambiguity but clunkier syntax. That's why it came up. :-)
Thank you for clearing that up :)
Noted. Maybe this will change in the future :-)
That would be wonderful!! I myself am very enthusiastic about C# and I am the first to admit that I have stupid language proposals which should not be accepted into any future language iterations (ok, I might have one or two good ideas, but no more than that :P). However, I think that the rather closed system of the language design process makes some people feel that the C# language design is not really "open-source" in that sense. (Do not get me wrong, the LDT is doing incredible work and I enjoy reading through the meeting notes every time they get published).
Maybe the LDT could be thinking about some kind of "podcast"? Meaning that they could publish an audio recording of each meeting or something like that. I do not know Microsoft's and the LDT's stance on privacy on that matter .... it's just an idea.
Thank you for having noted my concerns, @CyrusNajmabadi :)
@CyrusNajmabadi
Noted. Maybe this will change in the future :-)
I don't advocate for a voting process, but it would be nice to know that the LDT considers the feedback from the community at large. Granted, this specific community is probably the smallest venue for such feedback, but I can only assume that it's also one of the more engaged. What other channels are currently providing feedback for language decisions as they are happening? I imagine that there are other review sessions with groups within MS and with MVPs but at a much slower cadence. Too slow to handle the rapid design decisions that happened to records after the design review.
I do know that accepting such feedback is quick to lead to bikeshedding and other unreconcilable arguments leading back to the private protected
debacle, and at the end of the day it's the LDT's choice.
but it would be nice to know that the LDT considers the feedback from the community at large.
The feedback is def considerd. And, during this meeting, i think the position that you guys have been putting forth was well represented. That's why i'm ok just stating how our conclusions differed from yours. This is different, for example, from a case where we've made a decision but new information comes to light that we hadn't considered. That would carry a lot more weight for me as it might have affected the decision making.
Meaning that they could publish an audio recording of each meeting or something like that. I do not know Microsoft's and the LDT's stance on privacy on that matter .... it's just an idea.
We discussed this at the start of hte year, but rejected it. Maybe it might change in teh future.
Maybe the LDT could be thinking about some kind of "podcast"?
A separated out medium (i.e. not a recording of the meeting, but a discussion afterwards) might be something that could happen. Would entirely depend on people's interest/time in such a thing though.
@CyrusNajmabadi
This is different, for example, from a case where we've made a decision but new information comes to light that we hadn't considered.
I assume that the possibility of struct
+record
was acknowledged at that time, even though you had stated that there was hope that such a thing wouldn't be necessary and that struct
could just be a record. We now know that's not the case.
I assume that the possibility of struct+record was acknowledged at that time
The possibility of wanting a stack-alloc'ed record was def acknowledged at the time. We talked about it a lot and did not feel that this decision impacted the ability to do that in a way we would be comfortable with at that point.
you had stated that there was hope that such a thing wouldn't be necessary and that struct could just be a record. We now know that's not the case.
Well, it's not that it's not hte case. It's just that it likely couldn't be done 100% safely. Whether that's acceptable or not will have to be determined. And even if it can't be done, we determined that it did not change our thinking on reference type record
s.
I really don't see how record
for a class and record struct
for a struct is any worse than ending up with enum
for a value-typed enum and enum class
for a new DU concept.
and
enum class
for a new DU concept.
Well, no such syntax has been decided on for DU's. I'd probably go with union
TBH :)
Whether or not you decide on it, I still think the hypothetical enum class
syntax that has been shown by the team would be a fine option. And if that's fine, the record
/record struct
pair would be fine.
enum class
looks too much like enumclaw
to be seriously considered as a language feature.
just my 2cents - I was really looking fw to the records feature, I thought that finally we will have the ability to easily create 'Data' in the functional/clojure way, that is Data will be both immutable and have value semantics .. what we got instead is just some sugar over existing classes which IMO is just confusing (ie two ways of creating records which give different default accessibility etc, and ie having with
methods for a type that can be mutable), basically to me a very watered down and not that useful version of something that could have been really wonderful .. I really don't understand why it was done like that
Data will be both immutable and have value semantics ..
Data will be read only and have value semantics.
and ie having with methods for a type that can be mutable
with
will return a new instance, keeping the values distinct.
thanks @CyrusNajmabadi
Data will be read only and have value semantics.
I think there was an opportunity here for stronger immutability (record members being themselves only records or basic types or types marked by an attribute etc)
with will return a new instance, keeping the values distinct.
Yes but there is no point or use for that for a mutable type
It is only when one work with proper 'Data' that you get how incredibly useful and desirable they are, but seems like the record feature missed that and just went for sugar to save some typing :(
I think there was an opportunity here for stronger immutability (record members being themselves only records or basic types or types marked by an attribute etc)
You could certainly do that yourself with your own analyzer. The language doesn't want to overly limit for everyone. In other words, if we overly limit, then people who want flexibility are unable to get it. However, if we make it more flexible, then people who want limitations can easily impose them themselves.
Yes but there is no point or use for that for a mutable type
Sure there is.
It is only when one work with proper 'Data
What is "proper data"?
that you get how incredibly useful and desirable they are, but seems like the record feature missed that
I don't see how. You can literally use records as they exist today to do the type of coding you've just described. i.e. it would be trivial for you to write an analyzer that limits "record members being themselves only records or basic types or types marked by an attribute etc".
it would be trivial for you to write an analyzer
Not so trivial, especially since records also permit overriding Equals and GetHashCode etc, but anyway even if I do, that is very different from this being the explicit intention of the language.
What is "proper data"?
Well this is the crux of it really, it was my (mistaken) belief that the C# team understands this and is working towards it with records but that is not the case. There is no point for me to try to explain it here as many others (most clearly Rich Hickey) have done great job showing that at length. I will just say that working as exclusively as possible with stateless types that are strongly immutable with value semantics (Data) gives you programming superpowers.
I think I'll leave it at that thanks for your responses.
Not so trivial, especially since records also permit overriding Equals and GetHashCode etc, but anyway even if I do, that is very different from this being the explicit intention of the language.
The explicit intent is to make it simple for people to make these types, without undue restrictions that might not be desirable for all groups. If you want additional restrictions it would be really trivial to add that in an analyzer. For example, it would likely be a dozen or so lines to ensure that "record members being themselves only records or basic types or types marked by an attribute etc".
especially since records also permit overriding Equals and GetHashCode etc
I'm not sure why that's an issue.
There is no point for me to try to explain it here
If you're not going to explain it, then how would you expect others to understand your position on this? WHy is your view on "proper data" any more valid or appropriate than any other view?
I will just say that working as exclusively as possible with stateless types that are strongly immutable with value semantics (Data) gives you programming superpowers.
I don't see anything about records that prevents you from doing that. I presume stateless just means the same as "strongly immutable". In which case, you can trivially use records to get strongly immutable data with value semantics. As such, you will have the superpowers you want.
especially since records also permit overriding Equals and GetHashCode etc I'm not sure why that's an issue.
well if someone can override ie a record's Equals there is no more guarantee that type still have value semantics, I guess the analyzer can mandate Equals is not overridden
If you're not going to explain it
you can see ie "the value of values" https://www.youtube.com/watch?v=-6BsiVyC1kM (which is a classic), coupling data with state as in OOP leads to many kinds of bad.
I will look at writing an analyzer for this.
guess the analyzer can mandate Equals is not overridden
That seems like a bad idea. For example, if I have record Something (ImmutableArray<string> Items)
, how would I get value semantics without overriding Equals?
leads to many kinds of bad.
How are the coupled? There is nothing about records that mandates this.
Note, if you want pure data where you can't attach anything else, then just use a tuple.
That seems like a bad idea. For example, if I have record Something (ImmutableArray
Items), how would I get value semantics without overriding Equals?
exactly - I was hoping record would not allow ie using ImmutableArray here as it itself does not have value semantics, I created (which was trivial) thin wrappers around the immutable containers that have value semantics and are therefore 'Data' .. this opens a whole lot of new possibilities of composing such Data containers in records or other Data containers etc and then using the result in Hashset etc and also knowing they are always thread safe (though can get stale) etc .. (of course one has to be careful with performance here)
I think I will finish commenting on this here
exactly - I was hoping record would not allow ie using ImmutableArray here
That seems terribly limiting.
here as it itself does not have value semantics,
Why should i be limited from using it though? Nothing stops me from having value semantics in this case. You're basically stating that it now needs to be all-or-nothing for every single type used for records. That makes them substantially limited (basically, practically useless for existing code).
I created (which was trivial) thin wrappers around the immutable containers that have value semantics and are therefore 'Data'
This is not helpful at all. If i do this, now i cannot pass these wrappers to existing code taht expects the underlying type. And, if i can access/unwrap to get that underlyign data, then this wrapping is buying my nothing but extra work to support something that we can just support out of the box. In other words, you'd forcing jumping through hoops for a totally normal and reasonable case.
this opens a whole lot of new possibilities of composing such Data containers in records or other Data containers etc and then using the result in Hashset etc and also knowing they are always thread safe
You can have that with records today already.
Again, i think what you're conflating is that we allow more than what you want, and thus that means you can't get what you want. That's not hte case. If you want less than what is possible you can always have that. Just restrict usage of the parts you don't want. That's easy to do (and precisely what analyzers are good for).
This is an important part of design here because the opposite is not possible. If we restrict this, then people who want this flexibility and capability are just SOL. However, if we do not restrict this, then those people can use the feature, and people who like restrictions can get it. This allows a strictly greater set of users and use cases, without preventing any of the important use cases either group wants.
I'd like to ask the LDT to consider changing the current record-syntax to
record class MyRecord(...)
instead ofrecord MyRecord(...)
.... why?
I would want to propose the change for the following reasons:
class
keyword from a record declaration. Furthermore, Visual Studio displays the following when hovering over the record's identifier:record struct
proposal (maybe planned for C# 10?)class
/struct
-declarations (without explicitly using the concept of records).class
-keyword in a later iteration of C# .... the other way around would be a breaking change.The alternative would be to change VS' tooltips and syntax highlighting, in order to hide the
class
-implementation of the record-concept from the developer.I hope that there is still enough time left before November (the month in which C#9 will be released to the larger public).